Package org.eclipse.jdt.internal.compiler.lookup

Source Code of org.eclipse.jdt.internal.compiler.lookup.ConstraintExpressionFormula

/*******************************************************************************
* Copyright (c) 2013, 2014 GK Software AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     Stephan Herrmann - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.ExpressionContext;
import org.eclipse.jdt.internal.compiler.ast.FunctionalExpression;
import org.eclipse.jdt.internal.compiler.ast.Invocation;
import org.eclipse.jdt.internal.compiler.ast.LambdaExpression;
import org.eclipse.jdt.internal.compiler.ast.ReferenceExpression;
import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.lookup.InferenceContext18.SuspendedInferenceRecord;

/**
* Implementation of 18.1.2 in JLS8, case:
* <ul>
* <li>Expression -> T</li>
* </ul>
*/
class ConstraintExpressionFormula extends ConstraintFormula {
  Expression left;

  // this flag contributes to the workaround controlled by InferenceContext18.ARGUMENT_CONSTRAINTS_ARE_SOFT:
  boolean isSoft;

  ConstraintExpressionFormula(Expression expression, TypeBinding type, int relation) {
    this.left = expression;
    this.right = type;
    this.relation = relation;
  }
 
  ConstraintExpressionFormula(Expression expression, TypeBinding type, int relation, boolean isSoft) {
    this(expression, type, relation);
    this.isSoft = isSoft;
  }

  public Object reduce(InferenceContext18 inferenceContext) throws InferenceFailureException {
    // JLS 18.2.1
    proper:
    if (this.right.isProperType(true)) {
      TypeBinding exprType = this.left.resolvedType;
      if (exprType == null) {
        // if we get here for some kinds of poly expressions (incl. ConditionalExpression),
        // then other ways for checking compatibility are needed:
                if (this.left instanceof FunctionalExpression) {
                    if (this.left instanceof LambdaExpression) {
                        // cf. NegativeLambdaExpressionTest.test412453()
                        LambdaExpression copy = ((LambdaExpression) this.left).getResolvedCopyForInferenceTargeting(this.right);
                        return (copy != null && copy.resolvedType != null && copy.resolvedType.isValidBinding()) ? TRUE : FALSE;
                    }
                }
                return this.left.isCompatibleWith(this.right, inferenceContext.scope) ? TRUE : FALSE;
      } else if (!exprType.isValidBinding()) {
        return FALSE;
      }
      if (isCompatibleWithInLooseInvocationContext(exprType, this.right, inferenceContext)) {
        return TRUE;
      } else if (this.left instanceof AllocationExpression && this.left.isPolyExpression()) {
        // half-resolved diamond has a resolvedType, but that may not be the final word, try one more step of resolution:
              MethodBinding binding = ((AllocationExpression) this.left).binding(this.right, false, null);
              return (binding != null && binding.declaringClass.isCompatibleWith(this.right, inferenceContext.scope)) ? TRUE : FALSE;
            } else if (this.left instanceof Invocation && this.left.isPolyExpression()) {
              Invocation invoc = (Invocation) this.left;
              MethodBinding binding = invoc.binding(this.right, false, null);
              if (binding instanceof ParameterizedGenericMethodBinding) {
                ParameterizedGenericMethodBinding method = (ParameterizedGenericMethodBinding) binding;
          InferenceContext18 leftCtx = invoc.getInferenceContext(method);
                if (leftCtx.stepCompleted < InferenceContext18.TYPE_INFERRED) {
                  break proper; // fall through into nested inference below (not explicit in the spec!)
                }
              }
            }
      return FALSE;
    }
    if (!canBePolyExpression(this.left)) {
      TypeBinding exprType = this.left.resolvedType;
      if (exprType == null || !exprType.isValidBinding())
        return FALSE;
      return ConstraintTypeFormula.create(exprType, this.right, COMPATIBLE, this.isSoft);
    } else {
      // shapes of poly expressions (18.2.1)
      // - parenthesized expression : these are transparent in our AST
      if (this.left instanceof Invocation) {
        Invocation invocation = (Invocation) this.left;
        MethodBinding previousMethod = invocation.binding(this.right, false, null);
        if (previousMethod == null)    // can happen, e.g., if inside a copied lambda with ignored errors
          return null;         // -> proceed with no new constraints
        MethodBinding method = previousMethod;
        // ignore previous (inner) inference result and do a fresh start:
        // avoid original(), since we only want to discard one level of instantiation
        // (method type variables - not class type variables)!
        method = previousMethod.shallowOriginal();
        SuspendedInferenceRecord prevInvocation = inferenceContext.enterPolyInvocation(invocation, invocation.arguments());

        // Invocation Applicability Inference: 18.5.1 & Invocation Type Inference: 18.5.2
        try {
          Expression[] arguments = invocation.arguments();
          TypeBinding[] argumentTypes = arguments == null ? Binding.NO_PARAMETERS : new TypeBinding[arguments.length];
          for (int i = 0; i < argumentTypes.length; i++)
            argumentTypes[i] = arguments[i].resolvedType;
          if (previousMethod instanceof ParameterizedGenericMethodBinding) {
            // find the previous inner inference context to see what inference kind this invocation needs:
            InferenceContext18 innerCtx = invocation.getInferenceContext((ParameterizedGenericMethodBinding) previousMethod);
            if (innerCtx == null) { // no inference -> assume it wasn't really poly after all
              TypeBinding exprType = this.left.resolvedType;
              if (exprType == null || !exprType.isValidBinding())
                return FALSE;
              return ConstraintTypeFormula.create(exprType, this.right, COMPATIBLE, this.isSoft);
            }
            inferenceContext.inferenceKind = innerCtx.inferenceKind;
            innerCtx.outerContext = inferenceContext;
          }
          boolean isDiamond = method.isConstructor() && this.left.isPolyExpression(method);
          inferInvocationApplicability(inferenceContext, method, argumentTypes, isDiamond, inferenceContext.inferenceKind);
          if (!inferPolyInvocationType(inferenceContext, invocation, this.right, method))
            return FALSE;
          return null; // already incorporated
        } finally {
          inferenceContext.resumeSuspendedInference(prevInvocation);
        }
      } else if (this.left instanceof ConditionalExpression) {
        ConditionalExpression conditional = (ConditionalExpression) this.left;
        return new ConstraintFormula[] {
          new ConstraintExpressionFormula(conditional.valueIfTrue, this.right, this.relation, this.isSoft),
          new ConstraintExpressionFormula(conditional.valueIfFalse, this.right, this.relation, this.isSoft)
        };
      } else if (this.left instanceof LambdaExpression) {
        LambdaExpression lambda = (LambdaExpression) this.left;
        BlockScope scope = lambda.enclosingScope;
        if (!this.right.isFunctionalInterface(scope))
          return FALSE;
       
        ReferenceBinding t = (ReferenceBinding) this.right;
        ParameterizedTypeBinding withWildCards = InferenceContext18.parameterizedWithWildcard(t);
        if (withWildCards != null) {
          t = findGroundTargetType(inferenceContext, scope, lambda, withWildCards);
        }
        if (t == null)
          return FALSE;
        MethodBinding functionType = t.getSingleAbstractMethod(scope, true);
        if (functionType == null)
          return FALSE;
        TypeBinding[] parameters = functionType.parameters;
        if (parameters.length != lambda.arguments().length)
          return FALSE;
        if (lambda.argumentsTypeElided())
          for (int i = 0; i < parameters.length; i++)
            if (!parameters[i].isProperType(true))
              return FALSE;
        lambda = lambda.getResolvedCopyForInferenceTargeting(t);
        if (lambda == null)
          return FALSE; // not strictly unreduceable, but proceeding with TRUE would likely produce secondary errors
        if (functionType.returnType == TypeBinding.VOID) {
          if (!lambda.isVoidCompatible())
            return FALSE;
        } else {
          if (!lambda.isValueCompatible())
            return FALSE;
        }
        List<ConstraintFormula> result = new ArrayList<ConstraintFormula>();
        if (!lambda.argumentsTypeElided()) {
          Argument[] arguments = lambda.arguments();
          for (int i = 0; i < parameters.length; i++)
            result.add(ConstraintTypeFormula.create(parameters[i], arguments[i].type.resolveType(lambda.enclosingScope), SAME));
          // in addition, ⟨T' <: T⟩:
          if (lambda.resolvedType != null)
            result.add(ConstraintTypeFormula.create(lambda.resolvedType, this.right, SUBTYPE));
        }
        if (functionType.returnType != TypeBinding.VOID) {
          TypeBinding r = functionType.returnType;
          Expression[] exprs;
          if (lambda.body() instanceof Expression) {
            exprs = new Expression[] {(Expression)lambda.body()};
          } else {
            exprs = lambda.resultExpressions();
          }
          for (int i = 0; i < exprs.length; i++) {
            Expression expr = exprs[i];
            if (r.isProperType(true) && expr.resolvedType != null) {
              TypeBinding exprType = expr.resolvedType;
              // "not compatible in an assignment context with R"?
              if (!(expr.isConstantValueOfTypeAssignableToType(exprType, r)
                  || exprType.isCompatibleWith(r) || expr.isBoxingCompatible(exprType, r, expr, scope)))
                return FALSE;
            } else {
              result.add(new ConstraintExpressionFormula(expr, r, COMPATIBLE, this.isSoft));
            }
          }
        }
        if (result.size() == 0)
          return TRUE;
        return result.toArray(new ConstraintFormula[result.size()]);
      } else if (this.left instanceof ReferenceExpression) {
        return reduceReferenceExpressionCompatibility((ReferenceExpression) this.left, inferenceContext);
      }
    }
    return FALSE;
  }

  public ReferenceBinding findGroundTargetType(InferenceContext18 inferenceContext, BlockScope scope,
                          LambdaExpression lambda, ParameterizedTypeBinding targetTypeWithWildCards)
  {
    if (lambda.argumentsTypeElided()) {
      return lambda.findGroundTargetTypeForElidedLambda(scope, targetTypeWithWildCards);
    } else {
      SuspendedInferenceRecord previous = inferenceContext.enterLambda(lambda);
      try {
        return inferenceContext.inferFunctionalInterfaceParameterization(lambda, scope, targetTypeWithWildCards);
      } finally {
        inferenceContext.resumeSuspendedInference(previous);
      }
    }
  }

  private boolean canBePolyExpression(Expression expr) {
    // when inferring compatibility against a right type, the check isPolyExpression
    // must assume that expr occurs in s.t. like an assignment context:
    ExpressionContext previousExpressionContext = expr.getExpressionContext();
    if (previousExpressionContext == ExpressionContext.VANILLA_CONTEXT)
      this.left.setExpressionContext(ExpressionContext.ASSIGNMENT_CONTEXT);
    try {
      return expr.isPolyExpression();
    } finally {
      expr.setExpressionContext(previousExpressionContext);
    }
  }

  private Object reduceReferenceExpressionCompatibility(ReferenceExpression reference, InferenceContext18 inferenceContext) {
    TypeBinding t = this.right;
    if (t.isProperType(true))
      throw new IllegalStateException("Should not reach here with T being a proper type"); //$NON-NLS-1$
    if (!t.isFunctionalInterface(inferenceContext.scope))
      return FALSE;
    MethodBinding functionType = t.getSingleAbstractMethod(inferenceContext.scope, true);
    if (functionType == null)
      return FALSE;
    // potentially-applicable method for the method reference when targeting T (15.28.1),
    MethodBinding potentiallyApplicable = reference.findCompileTimeMethodTargeting(t, inferenceContext.scope);
    if (potentiallyApplicable == null)
      return FALSE;
    if (reference.isExactMethodReference()) {
      List<ConstraintFormula> newConstraints = new ArrayList<ConstraintFormula>();
      TypeBinding[] p = functionType.parameters;
      int n = p.length;
      TypeBinding[] pPrime = potentiallyApplicable.parameters;
      int k = pPrime.length;
      int offset = 0;
      if (n == k+1) {
        newConstraints.add(ConstraintTypeFormula.create(p[0], reference.lhs.resolvedType, COMPATIBLE));
        offset = 1;
      }
      for (int i = offset; i < n; i++)
        newConstraints.add(ConstraintTypeFormula.create(p[i], pPrime[i-offset], COMPATIBLE));
      TypeBinding r = functionType.returnType;
      if (r != TypeBinding.VOID) {
        TypeBinding rAppl = potentiallyApplicable.isConstructor() && !reference.isArrayConstructorReference() ? potentiallyApplicable.declaringClass : potentiallyApplicable.returnType;
        if (rAppl == TypeBinding.VOID)
          return FALSE;
        TypeBinding rPrime = rAppl.capture(inferenceContext.scope, 14); // FIXME capture position??
        newConstraints.add(ConstraintTypeFormula.create(rPrime, r, COMPATIBLE));
      }
      return newConstraints.toArray(new ConstraintFormula[newConstraints.size()]);
    } else { // inexact
      int n = functionType.parameters.length;
      for (int i = 0; i < n; i++)
        if (!functionType.parameters[i].isProperType(true))
          return FALSE;
      // Otherwise, a search for a compile-time declaration is performed, as defined in 15.28.1....
      // Note: we currently don't distinguish search for a potentially-applicable method from searching the compiler-time declaration,
      // hence reusing the method binding from above
      MethodBinding compileTimeDecl = potentiallyApplicable;
      if (!compileTimeDecl.isValidBinding())
        return FALSE;
      TypeBinding r = functionType.isConstructor() ? functionType.declaringClass : functionType.returnType;
      if (r.id == TypeIds.T_void)
        return TRUE;
      // ignore parameterization of resolve result and do a fresh start:
      MethodBinding original = compileTimeDecl.original();
      if (reference.typeArguments == null
          && ((original.typeVariables() != Binding.NO_TYPE_VARIABLES && r.mentionsAny(original.typeVariables(), -1))
            || (original.isConstructor() && original.declaringClass.typeVariables() != Binding.NO_TYPE_VARIABLES)))
              // not checking r.mentionsAny for constructors, because A::new resolves to the raw type
              // whereas in fact the type of all expressions of this shape depends on their type variable (if any)
      {
        SuspendedInferenceRecord prevInvocation = inferenceContext.enterPolyInvocation(reference, null/*no invocation arguments available*/);

        // Invocation Applicability Inference: 18.5.1 & Invocation Type Inference: 18.5.2
        try {
          inferInvocationApplicability(inferenceContext, original, functionType.parameters, original.isConstructor()/*mimic a diamond?*/, inferenceContext.inferenceKind);
          if (!inferPolyInvocationType(inferenceContext, reference, r, original))
            return FALSE;
          if (!original.isConstructor()
              || reference.receiverType.isRawType()  // note: rawtypes may/may not have typeArguments() depending on initialization state
              || reference.receiverType.typeArguments() == null)
            return null; // already incorporated
          // for Foo<Bar>::new we need to (illegally) add one more constraint below to get to the Bar
        } catch (InferenceFailureException e) {
          return FALSE;
        } finally {
          inferenceContext.resumeSuspendedInference(prevInvocation);
        }
      }
      TypeBinding rPrime = compileTimeDecl.isConstructor() ? compileTimeDecl.declaringClass : compileTimeDecl.returnType;
      if (rPrime.id == TypeIds.T_void)
        return FALSE;
      return ConstraintTypeFormula.create(rPrime, r, COMPATIBLE, this.isSoft);
    }
  }

  static void inferInvocationApplicability(InferenceContext18 inferenceContext, MethodBinding method, TypeBinding[] arguments, boolean isDiamond, int checkType)
  {
    // 18.5.1
    TypeVariableBinding[] typeVariables = method.typeVariables;
    if (isDiamond) {
      TypeVariableBinding[] classTypeVariables = method.declaringClass.typeVariables();
      int l1 = typeVariables.length;
      int l2 = classTypeVariables.length;
      if (l1 == 0) {
        typeVariables = classTypeVariables;
      } else if (l2 != 0) {
        System.arraycopy(typeVariables, 0, typeVariables=new TypeVariableBinding[l1+l2], 0, l1);
        System.arraycopy(classTypeVariables, 0, typeVariables, l1, l2);
      }       
    }
    TypeBinding[] parameters = method.parameters;
    InferenceVariable[] inferenceVariables = inferenceContext.createInitialBoundSet(typeVariables); // creates initial bound set B

    // check if varargs need special treatment:
    int paramLength = method.parameters.length;
    TypeBinding varArgsType = null;
    if (method.isVarargs()) {
      int varArgPos = paramLength-1;
      varArgsType = method.parameters[varArgPos];
    }
    inferenceContext.createInitialConstraintsForParameters(parameters, checkType==InferenceContext18.CHECK_VARARG, varArgsType, method);
    inferenceContext.addThrowsContraints(typeVariables, inferenceVariables, method.thrownExceptions);
  }

  static boolean inferPolyInvocationType(InferenceContext18 inferenceContext, InvocationSite invocationSite, TypeBinding targetType, MethodBinding method)
        throws InferenceFailureException
  {
    TypeBinding[] typeArguments = invocationSite.genericTypeArguments();
    if (typeArguments == null) {
      // invocation type inference (18.5.2):
      TypeBinding returnType = method.isConstructor() ? method.declaringClass : method.returnType;
      if (returnType == TypeBinding.VOID)
        throw new InferenceFailureException("expression has no value"); //$NON-NLS-1$

      if (inferenceContext.usesUncheckedConversion()) {
        // spec says erasure, but we don't really have compatibility rules for erasure, use raw type instead:
        TypeBinding erasure = inferenceContext.environment.convertToRawType(returnType, false);
        ConstraintTypeFormula newConstraint = ConstraintTypeFormula.create(erasure, targetType, COMPATIBLE);
        if (!inferenceContext.reduceAndIncorporate(newConstraint))
          return false;
        // continuing at true is not spec'd but needed for javac-compatibility,
        // see org.eclipse.jdt.core.tests.compiler.regression.GenericsRegressionTest_1_8.testBug428198()
        // and org.eclipse.jdt.core.tests.compiler.regression.GenericsRegressionTest_1_8.testBug428264()
      }
      TypeBinding rTheta = inferenceContext.substitute(returnType);
      ParameterizedTypeBinding parameterizedType = InferenceContext18.parameterizedWithWildcard(rTheta);
      if (parameterizedType != null && parameterizedType.arguments != null) {
        TypeBinding[] arguments = parameterizedType.arguments;
        InferenceVariable[] betas = inferenceContext.addTypeVariableSubstitutions(arguments);
        ParameterizedTypeBinding gbeta = inferenceContext.environment.createParameterizedType(
            parameterizedType.genericType(), betas, parameterizedType.enclosingType(), parameterizedType.getTypeAnnotations());
        inferenceContext.currentBounds.captures.put(gbeta, parameterizedType); // established: both types have nonnull arguments
        ConstraintTypeFormula newConstraint = ConstraintTypeFormula.create(gbeta, targetType, COMPATIBLE);
        return inferenceContext.reduceAndIncorporate(newConstraint);
      }
      if (rTheta instanceof InferenceVariable) {
        InferenceVariable alpha = (InferenceVariable) rTheta;
        boolean toResolve = false;
        if (inferenceContext.currentBounds.condition18_5_2_bullet_3_3_1(alpha, targetType)) {
          toResolve = true;
        } else if (inferenceContext.currentBounds.condition18_5_2_bullet_3_3_2(alpha, targetType, inferenceContext)) {
          toResolve = true;
        } else if (targetType.isPrimitiveType()) {
          TypeBinding wrapper = inferenceContext.currentBounds.findWrapperTypeBound(alpha);
          if (wrapper != null)
            toResolve = true;
        }
        if (toResolve) {
          BoundSet solution = inferenceContext.solve(new InferenceVariable[]{alpha});
          if (solution == null)
            return false;
          TypeBinding u = solution.getInstantiation(alpha, null).capture(inferenceContext.scope, invocationSite.sourceStart()); // TODO make position unique?
          ConstraintTypeFormula newConstraint = ConstraintTypeFormula.create(u, targetType, COMPATIBLE);
          return inferenceContext.reduceAndIncorporate(newConstraint);
        }
      }
      ConstraintTypeFormula newConstraint = ConstraintTypeFormula.create(rTheta, targetType, COMPATIBLE);
      if (!inferenceContext.reduceAndIncorporate(newConstraint))
        return false;
    }
    return true;
  }

  Collection<InferenceVariable> inputVariables(final InferenceContext18 context) {
    // from 18.5.2.
    if (this.left instanceof LambdaExpression) {
      if (this.right instanceof InferenceVariable) {
        return Collections.singletonList((InferenceVariable)this.right);
      }
      if (this.right.isFunctionalInterface(context.scope)) {
        LambdaExpression lambda = (LambdaExpression) this.left;
        MethodBinding sam = this.right.getSingleAbstractMethod(context.scope, true); // TODO derive with target type?
        final Set<InferenceVariable> variables = new HashSet<InferenceVariable>();
        if (lambda.argumentsTypeElided()) {
          // i)
          int len = sam.parameters.length;
          for (int i = 0; i < len; i++) {
            sam.parameters[i].collectInferenceVariables(variables);
          }
        }
        if (sam.returnType != TypeBinding.VOID) {
          // ii)
          final TypeBinding r = sam.returnType;
          Statement body = lambda.body();
          if (body instanceof Expression) {
            variables.addAll(new ConstraintExpressionFormula((Expression) body, r, COMPATIBLE).inputVariables(context));
          } else {
            // TODO: should I use LambdaExpression.resultExpressions? (is currently private).
            body.traverse(new ASTVisitor() {
              public boolean visit(ReturnStatement returnStatement, BlockScope scope) {
                variables.addAll(new ConstraintExpressionFormula(returnStatement.expression, r, COMPATIBLE).inputVariables(context));
                return false;
              }
            }, (BlockScope)null);
          }
        }
        return variables;
      }
    } else if (this.left instanceof ReferenceExpression) {
      if (this.right instanceof InferenceVariable) {
        return Collections.singletonList((InferenceVariable)this.right);
      }
      if (this.right.isFunctionalInterface(context.scope) && !this.left.isExactMethodReference()) {
        MethodBinding sam = this.right.getSingleAbstractMethod(context.scope, true);
        final Set<InferenceVariable> variables = new HashSet<InferenceVariable>();
        int len = sam.parameters.length;
        for (int i = 0; i < len; i++) {
          sam.parameters[i].collectInferenceVariables(variables);
        }
        return variables;
      }     
    } else if (this.left instanceof ConditionalExpression && this.left.isPolyExpression()) {
      ConditionalExpression expr = (ConditionalExpression) this.left;
      Set<InferenceVariable> variables = new HashSet<InferenceVariable>();
      variables.addAll(new ConstraintExpressionFormula(expr.valueIfTrue, this.right, COMPATIBLE).inputVariables(context));
      variables.addAll(new ConstraintExpressionFormula(expr.valueIfFalse, this.right, COMPATIBLE).inputVariables(context));
      return variables;
    }
    return EMPTY_VARIABLE_LIST;
  }

  // debugging:
  public String toString() {
    StringBuffer buf = new StringBuffer().append(LEFT_ANGLE_BRACKET);
    this.left.printExpression(4, buf);
    buf.append(relationToString(this.relation));
    appendTypeName(buf, this.right);
    buf.append(RIGHT_ANGLE_BRACKET);
    return buf.toString();
  }
}
TOP

Related Classes of org.eclipse.jdt.internal.compiler.lookup.ConstraintExpressionFormula

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.