From 96325f3db984fe6a047ec2aeb000809d229125ab Mon Sep 17 00:00:00 2001 From: mmews Date: Thu, 22 Feb 2024 14:15:01 +0100 Subject: [PATCH] migrate migrate --- .../n4js/postprocessing/PolyProcessor.java | 267 ++++++++++++++++++ .../n4js/postprocessing/PolyProcessor.xtend | 260 ----------------- .../eclipse/n4js/types/utils/LambdaUtils.java | 96 +++++++ .../n4js/types/utils/LambdaUtils.xtend | 88 ------ .../eclipse/n4js/types/utils/TypeHelper.java | 211 ++++++++++++++ .../eclipse/n4js/types/utils/TypeHelper.xtend | 194 ------------- .../validators/N4JSLambdaValidator.java | 3 +- 7 files changed, 576 insertions(+), 543 deletions(-) create mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor.java delete mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor.xtend create mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/LambdaUtils.java delete mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/LambdaUtils.xtend create mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/TypeHelper.java delete mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/TypeHelper.xtend diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor.java new file mode 100644 index 0000000000..2d07c2ade6 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor.java @@ -0,0 +1,267 @@ +/** + * Copyright (c) 2016 NumberFour 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: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.postprocessing; + +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.getCancelIndicator; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.n4JS.Argument; +import org.eclipse.n4js.n4JS.ArrayElement; +import org.eclipse.n4js.n4JS.ArrayLiteral; +import org.eclipse.n4js.n4JS.ConditionalExpression; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.n4JS.FunctionExpression; +import org.eclipse.n4js.n4JS.ObjectLiteral; +import org.eclipse.n4js.n4JS.ParameterizedCallExpression; +import org.eclipse.n4js.n4JS.PropertyAssignment; +import org.eclipse.n4js.n4JS.PropertyMethodDeclaration; +import org.eclipse.n4js.n4JS.RelationalExpression; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.TypableElement; +import org.eclipse.n4js.ts.types.util.Variance; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.typesystem.N4JSTypeSystem; +import org.eclipse.n4js.typesystem.constraints.InferenceContext; +import org.eclipse.n4js.typesystem.constraints.TypeConstraint; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper; +import org.eclipse.n4js.utils.DeclMergingHelper; +import org.eclipse.n4js.utils.DestructureHelper; +import org.eclipse.n4js.validation.JavaScriptVariantHelper; +import org.eclipse.xtext.service.OperationCanceledManager; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * The main poly processor responsible for typing poly expressions using a constraint-based approach. + *

+ * It tells other processors which AST nodes it is responsible for (see + * {@link PolyProcessor#isResponsibleFor(TypableElement) isResponsibleFor()}) and which AST nodes are an entry point to + * constraint-based type inference (see {@link PolyProcessor#isEntryPoint(TypableElement) isEntryPoint()}). For those + * "entry points" method {@link PolyProcessor#inferType(RuleEnvironment,Expression,ASTMetaInfoCache) inferType()} should + * be invoked by the other processors (mainly the TypeProcessor). + */ +@Singleton +class PolyProcessor extends AbstractPolyProcessor { + + @Inject + private PolyProcessor_ArrayLiteral arrayLiteralProcessor; + @Inject + private PolyProcessor_ObjectLiteral objectLiteralProcessor; + @Inject + private PolyProcessor_FunctionExpression functionExpressionProcessor; + @Inject + private PolyProcessor_CallExpression callExpressionProcessor; + + @Inject + private N4JSTypeSystem ts; + @Inject + private TypeSystemHelper tsh; + @Inject + private DeclMergingHelper declMergingHelper; + + @Inject + private DestructureHelper destructureHelper; + + @Inject + private OperationCanceledManager operationCanceledManager; + + @Inject + private JavaScriptVariantHelper jsVariantHelper; + + // ################################################################################################################ + + /** + * Tells if the given AST node's type should be inferred through constraint-based type inference. In that case, no + * other processor is allowed to add a type for this node to the {@link ASTMetaInfoCache}! + */ + boolean isResponsibleFor(TypableElement astNode) { + EObject parent = astNode.eContainer(); + return isPoly(astNode) + || (astNode instanceof Argument && parent instanceof ParameterizedCallExpression && isPoly(parent)) + || (astNode instanceof FormalParameter && parent instanceof FunctionExpression && isPoly(parent)) + || (astNode instanceof FormalParameter && parent instanceof PropertyMethodDeclaration && isPoly(parent)) + || (astNode instanceof ArrayElement && parent instanceof ArrayLiteral && isPoly(parent)) + || (astNode instanceof PropertyAssignment && parent instanceof ObjectLiteral && isPoly(parent)); + // note in previous line: + // even if the PropertyAssignment itself is NOT poly, we claim responsibility for it if the containing + // ObjectLiteral is poly + } + + /** + * Tells if the given AST node is an entry point to constraint-based type inference. In that case, and only in that + * case, method {@link #inferType(RuleEnvironment,Expression,ASTMetaInfoCache) inferType()} must be invoked for this + * AST node. + */ + boolean isEntryPoint(TypableElement astNode) { + return isRootPoly(astNode); + } + + // ################################################################################################################ + + /** + * Main method for inferring the type of poly expressions, i.e. for constraint-based type inference. It should be + * invoked for all AST nodes for which method {@link #isEntryPoint(TypableElement) isEntryPoint()} returns + * true (and only for such nodes!). This will ensure that this method will be called for all roots of + * trees of nested poly expressions (including such trees that only consist of a root without children) but not for + * the nested children. + *

+ * This method, together with its delegates, is responsible for adding to the cache types for all the following AST + * nodes: + *

    + *
  1. the given root poly expression rootPoly, + *
  2. all nested child poly expressions, + *
  3. some nested elements that aren't expressions but closely belong to one of the above expressions, e.g. formal + * parameters contained in a function expression (see code of {@link #isResponsibleFor(TypableElement)} for which + * elements are included here). + *
+ *

+ * The overall process of constraint-based type inference is as follows: + *

    + *
  1. create a new, empty {@link InferenceContext} called IC. + *
  2. invoke method {@link #processExpr(RuleEnvironment,Expression,TypeRef,InferenceContext,ASTMetaInfoCache) + * #processExpr()} for the given root poly expression and all its direct and indirect child poly expressions. This + * will ... + *
      + *
    1. add to IC (i) inference variables for all types to be inferred and (ii) appropriate constraints + * derived from the poly expressions and their relations. + *
    2. register onSolved handlers to IC (see below what these handlers are doing). + *
    + *
  3. solve the entire constraint system, i.e. invoke {@link InferenceContext#solve()} on IC. + *
  4. once solution is done (no matter if successful or failed) IC will automatically trigger the + * onSolved handlers: + *
      + *
    • in the success case, the handlers will use the solution in IC to add types to the cache for the + * given root poly expression and all its nested child poly expressions (and also for contained, typable elements + * such as fpars of function expressions). + *
    • in the failure case, the handlers will add fall-back types to the cache. + *
    + *
+ */ + void inferType(RuleEnvironment G, Expression rootPoly, ASTMetaInfoCache cache) { + // create a new constraint system + InferenceContext infCtx = new InferenceContext(ts, tsh, declMergingHelper, operationCanceledManager, + getCancelIndicator(G), G); + + // in plain JS files, we want to avoid searching for a solution (to avoid performance problems in some JS files + // with extremely large array/object literals) but to avoid having to deal with this case with additional code, + // we still build a constraint system as usual (TEMPORARAY HACK) + // TODO find proper way to deal with extremely large array/object literals + if (jsVariantHelper.doomTypeInference(rootPoly)) { + infCtx.addConstraint(TypeConstraint.FALSE); + } + + TypeRef expectedTypeOfPoly = destructureHelper.calculateExpectedType(rootPoly, G, infCtx); + // we have to pass the expected type to the #getType() method, so retrieve it first + // (until the expectedType judgment is integrated into AST traversal, we have to invoke this judgment here; + // in case of not-well-behaving expectedType rules, we use 'null' as expected type, i.e. no expectation) + // TODO integrate expectedType judgment into AST traversal and remove #isProblematicCaseOfExpectedType() + TypeRef expectedTypeRef = null; + if (expectedTypeOfPoly != null) { + expectedTypeRef = expectedTypeOfPoly; + } else if (!isProblematicCaseOfExpectedType(rootPoly)) { + expectedTypeRef = ts.expectedType(G, rootPoly.eContainer(), rootPoly); + } + + // call #processExpr() (this will recursively call #processExpr() on nested expressions, even if non-poly) + TypeRef typeRef = processExpr(G, rootPoly, expectedTypeRef, infCtx, cache); + + // add constraint to ensure that type of 'rootPoly' is subtype of its expected type + if (!TypeUtils.isVoid(typeRef)) { + if (expectedTypeRef != null) { + infCtx.addConstraint(0, typeRef, expectedTypeRef, Variance.CO); + } + } + + // compute solution + // (note: we're not actually interested in the solution, here; we just want to make sure to trigger the + // onSolved handlers registered by the #process*() methods of the other poly processors; see responsibilities of + // #processExpr(RuleEnvironment, Expression, TypeRef, InferenceContext, ASTMetaInfoCache) + infCtx.solve(); + } + + /** + * Key method for handling poly expressions. + *

+ * It has the following responsibilities: + *

+ * IMPORTANT: the "temporary" type may contain inference variables; the "final" types must be proper, i.e. must not + * contain any inference variables! + */ + protected TypeRef processExpr(RuleEnvironment G, Expression expr, TypeRef expectedTypeRef, + InferenceContext infCtx, ASTMetaInfoCache cache) { + + if (isPoly(expr)) { + // poly -> delegate this to the appropriate, specific PolyProcessor + if (expr instanceof ArrayLiteral) { + return arrayLiteralProcessor.processArrayLiteral(G, (ArrayLiteral) expr, expectedTypeRef, infCtx, + cache); + } else if (expr instanceof ObjectLiteral) { + return objectLiteralProcessor.processObjectLiteral(G, (ObjectLiteral) expr, expectedTypeRef, infCtx, + cache); + } else if (expr instanceof FunctionExpression) { + return functionExpressionProcessor.processFunctionExpression(G, (FunctionExpression) expr, + expectedTypeRef, infCtx, cache); + } else if (expr instanceof ParameterizedCallExpression) { + return callExpressionProcessor.processCallExpression(G, (ParameterizedCallExpression) expr, + expectedTypeRef, infCtx, cache); + } else if (expr instanceof ConditionalExpression) { + ConditionalExpression ce = (ConditionalExpression) expr; + if (isPoly(ce.getTrueExpression())) { + TypeRef typeRef = processExpr(G, ce.getTrueExpression(), expectedTypeRef, infCtx, cache); + // store a copy of the inferred type also at the conditional expression node + infCtx.onSolved(solution -> cache.storeType(ce, + TypeUtils.copy(cache.getTypeFailSafe(ce.getTrueExpression())))); + return typeRef; + } else if (isPoly(ce.getFalseExpression())) { + TypeRef typeRef = processExpr(G, ce.getFalseExpression(), expectedTypeRef, infCtx, cache); + // store a copy of the inferred type also at the conditional expression node + infCtx.onSolved(solution -> cache.storeType(ce, + TypeUtils.copy(cache.getTypeFailSafe(ce.getFalseExpression())))); + return typeRef; + } else { + throw new IllegalArgumentException("missing case in #processExpr() for poly expression: " + expr); + } + } + throw new IllegalArgumentException("missing case in #processExpr() for poly expression: " + expr); + } else { + // not poly -> directly infer type via type system + TypeRef result = ts.type(G, expr); + // do *not* store in cache (TypeProcessor responsible for storing types of non-poly expressions in cache!) + return result; + } + } + + /** + * Returns true if we are not allowed to ask for the expected type of 'node', because this would lead to illegal + * forward references (temporary). + */ + private boolean isProblematicCaseOfExpectedType(EObject node) { + return node != null && node.eContainer() instanceof RelationalExpression; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor.xtend deleted file mode 100644 index 6d5ebba220..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor.xtend +++ /dev/null @@ -1,260 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour 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: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.postprocessing - -import com.google.inject.Inject -import com.google.inject.Singleton -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.n4JS.Argument -import org.eclipse.n4js.n4JS.ArrayElement -import org.eclipse.n4js.n4JS.ArrayLiteral -import org.eclipse.n4js.n4JS.ConditionalExpression -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.FormalParameter -import org.eclipse.n4js.n4JS.FunctionExpression -import org.eclipse.n4js.n4JS.ObjectLiteral -import org.eclipse.n4js.n4JS.ParameterizedCallExpression -import org.eclipse.n4js.n4JS.PropertyAssignment -import org.eclipse.n4js.n4JS.PropertyMethodDeclaration -import org.eclipse.n4js.n4JS.RelationalExpression -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.TypableElement -import org.eclipse.n4js.ts.types.util.Variance -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.typesystem.N4JSTypeSystem -import org.eclipse.n4js.typesystem.constraints.InferenceContext -import org.eclipse.n4js.typesystem.constraints.TypeConstraint -import org.eclipse.n4js.typesystem.utils.RuleEnvironment -import org.eclipse.n4js.typesystem.utils.TypeSystemHelper -import org.eclipse.n4js.utils.DeclMergingHelper -import org.eclipse.n4js.utils.DestructureHelper -import org.eclipse.n4js.validation.JavaScriptVariantHelper -import org.eclipse.xtext.service.OperationCanceledManager - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * The main poly processor responsible for typing poly expressions using a constraint-based approach. - *

- * It tells other processors which AST nodes it is responsible for (see {@link PolyProcessor#isResponsibleFor(TypableElement) isResponsibleFor()}) - * and which AST nodes are an entry point to constraint-based type inference (see {@link PolyProcessor#isEntryPoint(TypableElement) isEntryPoint()}). - * For those "entry points" method {@link PolyProcessor#inferType(RuleEnvironment,Expression,ASTMetaInfoCache) inferType()} - * should be invoked by the other processors (mainly the TypeProcessor). - */ -@Singleton -package class PolyProcessor extends AbstractPolyProcessor { - - @Inject - private PolyProcessor_ArrayLiteral arrayLiteralProcessor; - @Inject - private PolyProcessor_ObjectLiteral objectLiteralProcessor; - @Inject - private PolyProcessor_FunctionExpression functionExpressionProcessor; - @Inject - private PolyProcessor_CallExpression callExpressionProcessor; - - @Inject - private N4JSTypeSystem ts; - @Inject - private TypeSystemHelper tsh; - @Inject - private DeclMergingHelper declMergingHelper; - - @Inject - private DestructureHelper destructureHelper; - - @Inject - private OperationCanceledManager operationCanceledManager; - - @Inject - private JavaScriptVariantHelper jsVariantHelper; - - // ################################################################################################################ - - - /** - * Tells if the given AST node's type should be inferred through constraint-based type inference. In that case, - * no other processor is allowed to add a type for this node to the {@link ASTMetaInfoCache}! - */ - def package boolean isResponsibleFor(TypableElement astNode) { - astNode.isPoly - || (astNode instanceof Argument && astNode.eContainer instanceof ParameterizedCallExpression && astNode.eContainer.isPoly) - || (astNode instanceof FormalParameter && astNode.eContainer instanceof FunctionExpression && astNode.eContainer.isPoly) - || (astNode instanceof FormalParameter && astNode.eContainer instanceof PropertyMethodDeclaration && astNode.eContainer.isPoly) - || (astNode instanceof ArrayElement && astNode.eContainer instanceof ArrayLiteral && astNode.eContainer.isPoly) - || (astNode instanceof PropertyAssignment && astNode.eContainer instanceof ObjectLiteral && astNode.eContainer.isPoly) - // note in previous line: - // even if the PropertyAssignment itself is NOT poly, we claim responsibility for it if the containing ObjectLiteral is poly - } - - /** - * Tells if the given AST node is an entry point to constraint-based type inference. In that case, and only in that - * case, method {@link #inferType(RuleEnvironment,Expression,ASTMetaInfoCache) inferType()} must be invoked for this - * AST node. - */ - def package boolean isEntryPoint(TypableElement astNode) { - astNode.isRootPoly - } - - - // ################################################################################################################ - - - /** - * Main method for inferring the type of poly expressions, i.e. for constraint-based type inference. It should be - * invoked for all AST nodes for which method {@link #isEntryPoint(TypableElement) isEntryPoint()} - * returns true (and only for such nodes!). This will ensure that this method will be called for all - * roots of trees of nested poly expressions (including such trees that only consist of a root without children) but - * not for the nested children. - *

- * This method, together with its delegates, is responsible for adding to the cache types for all the following - * AST nodes: - *

    - *
  1. the given root poly expression rootPoly, - *
  2. all nested child poly expressions, - *
  3. some nested elements that aren't expressions but closely belong to one of the above expressions, e.g. formal - * parameters contained in a function expression (see code of {@link #isResponsibleFor(TypableElement)} for which - * elements are included here). - *
- *

- * The overall process of constraint-based type inference is as follows: - *

    - *
  1. create a new, empty {@link InferenceContext} called IC. - *
  2. invoke method {@link #processExpr(RuleEnvironment,Expression,TypeRef,InferenceContext,ASTMetaInfoCache) #processExpr()} - * for the given root poly expression and all its direct and indirect child poly expressions. This will ... - *
      - *
    1. add to IC (i) inference variables for all types to be inferred and (ii) appropriate - * constraints derived from the poly expressions and their relations. - *
    2. register onSolved handlers to IC (see below what these handlers are doing). - *
    - *
  3. solve the entire constraint system, i.e. invoke {@link #solve()} on IC. - *
  4. once solution is done (no matter if successful or failed) IC will automatically trigger the - * onSolved handlers: - *
      - *
    • in the success case, the handlers will use the solution in IC to add types to the cache for - * the given root poly expression and all its nested child poly expressions (and also for contained, typable - * elements such as fpars of function expressions). - *
    • in the failure case, the handlers will add fall-back types to the cache. - *
    - *
- */ - def package void inferType(RuleEnvironment G, Expression rootPoly, ASTMetaInfoCache cache) { - // create a new constraint system - val InferenceContext infCtx = new InferenceContext(ts, tsh, declMergingHelper, operationCanceledManager, G.cancelIndicator, G); - - // in plain JS files, we want to avoid searching for a solution (to avoid performance problems in some JS files - // with extremely large array/object literals) but to avoid having to deal with this case with additional code, - // we still build a constraint system as usual (TEMPORARAY HACK) - // TODO find proper way to deal with extremely large array/object literals - if (jsVariantHelper.doomTypeInference(rootPoly)) { - infCtx.addConstraint(TypeConstraint.FALSE); - } - - val expectedTypeOfPoly = destructureHelper.calculateExpectedType(rootPoly, G, infCtx); - // we have to pass the expected type to the #getType() method, so retrieve it first - // (until the expectedType judgment is integrated into AST traversal, we have to invoke this judgment here; - // in case of not-well-behaving expectedType rules, we use 'null' as expected type, i.e. no expectation) - // TODO integrate expectedType judgment into AST traversal and remove #isProblematicCaseOfExpectedType() - val expectedTypeRef = if (expectedTypeOfPoly !== null) { - expectedTypeOfPoly - } else if (!rootPoly.isProblematicCaseOfExpectedType) { - ts.expectedType(G, rootPoly.eContainer(), rootPoly); - }; - - // call #processExpr() (this will recursively call #processExpr() on nested expressions, even if non-poly) - val typeRef = processExpr(G, rootPoly, expectedTypeRef, infCtx, cache); - - // add constraint to ensure that type of 'rootPoly' is subtype of its expected type - if (!TypeUtils.isVoid(typeRef)) { - if (expectedTypeRef !== null) { - infCtx.addConstraint(0, typeRef, expectedTypeRef, Variance.CO); - } - } - - // compute solution - // (note: we're not actually interested in the solution, here; we just want to make sure to trigger the - // onSolved handlers registered by the #process*() methods of the other poly processors; see responsibilities of - // #processExpr(RuleEnvironment, Expression, TypeRef, InferenceContext, ASTMetaInfoCache) - infCtx.solve; - } - - - - /** - * Key method for handling poly expressions. - *

- * It has the following responsibilities: - *

- * IMPORTANT: the "temporary" type may contain inference variables; the "final" types must be proper, i.e. must not - * contain any inference variables! - */ - def protected TypeRef processExpr(RuleEnvironment G, Expression expr, TypeRef expectedTypeRef, - InferenceContext infCtx, ASTMetaInfoCache cache) { - - if (isPoly(expr)) { - // poly -> delegate this to the appropriate, specific PolyProcessor - return switch(expr) { - ArrayLiteral: - arrayLiteralProcessor.processArrayLiteral(G, expr, expectedTypeRef, infCtx, cache) - ObjectLiteral: - objectLiteralProcessor.processObjectLiteral(G, expr, expectedTypeRef, infCtx, cache) - FunctionExpression: - functionExpressionProcessor.processFunctionExpression(G, expr, expectedTypeRef, infCtx, cache) - ParameterizedCallExpression: - callExpressionProcessor.processCallExpression(G, expr, expectedTypeRef, infCtx, cache) - ConditionalExpression: - if (isPoly(expr.trueExpression)) { - val TypeRef typeRef = processExpr(G, expr.trueExpression, expectedTypeRef, infCtx, cache); - // store a copy of the inferred type also at the conditional expression node - infCtx.onSolved([ solution | cache.storeType(expr, TypeUtils.copy(cache.getTypeFailSafe(expr.trueExpression))) ]); - typeRef; - } else if (isPoly(expr.falseExpression)) { - val TypeRef typeRef = processExpr(G, expr.falseExpression, expectedTypeRef, infCtx, cache); - // store a copy of the inferred type also at the conditional expression node - infCtx.onSolved([ solution | cache.storeType(expr, TypeUtils.copy(cache.getTypeFailSafe(expr.falseExpression))) ]); - typeRef; - } else { - throw new IllegalArgumentException("missing case in #processExpr() for poly expression: " + expr) - } - default: - throw new IllegalArgumentException("missing case in #processExpr() for poly expression: " + expr) - }; - } else { - // not poly -> directly infer type via type system - val result = ts.type(G, expr); - // do *not* store in cache (TypeProcessor responsible for storing types of non-poly expressions in cache!) - return result; - } - } - - /** - * Returns true if we are not allowed to ask for the expected type of 'node', because this would lead to illegal - * forward references (temporary). - */ - def private boolean isProblematicCaseOfExpectedType(EObject node) { - return node?.eContainer instanceof RelationalExpression - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/LambdaUtils.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/LambdaUtils.java new file mode 100644 index 0000000000..e72cae6ac9 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/LambdaUtils.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2016 NumberFour 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: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.types.utils; + +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.filter; + +import java.util.Iterator; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.n4JS.ArrowFunction; +import org.eclipse.n4js.n4JS.Block; +import org.eclipse.n4js.n4JS.FunctionDefinition; +import org.eclipse.n4js.n4JS.FunctionExpression; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4JSASTUtils; +import org.eclipse.n4js.n4JS.ThisArgProvider; +import org.eclipse.n4js.n4JS.ThisLiteral; +import org.eclipse.n4js.n4JS.ThisTarget; +import org.eclipse.n4js.utils.EcoreUtilN4; +import org.eclipse.xtext.EcoreUtil2; + +/** + * Utility methods added to support ES6 arrow functions, aka lambdas. + */ +public class LambdaUtils { + + /** + * All {@link ThisLiteral}s occurring in body that refer to the same this-value; ie without delving + * into functions (unless arrow functions) or into a declaration that introduces a this context, ie any + * {@link ThisTarget} in general. + */ + public static Iterator thisLiterals(Block body) { + return filter(EcoreUtilN4.getAllContentsFiltered(body, elem -> !introducesThisContext(elem)), + ThisLiteral.class); + } + + private static boolean introducesThisContext(EObject node) { + if (node instanceof FunctionExpression) { + return !((FunctionExpression) node).isArrowFunction(); + } + if (node instanceof FunctionDefinition) { + return true; + } + if (node instanceof FunctionDefinition) { + return true; + } + return false; + } + + /** Returns true iff the given element is instance of {@link ArrowFunction} */ + public static boolean isLambda(EObject funDef) { + return funDef instanceof ArrowFunction; + } + + /** + * A this-binder is a non-lambda {@link FunctionDefinition}. This method looks up the nearest such construct that + * encloses the argument. In case the argument itself is a this-binder, it is returned. In case no this-binder + * exists (for example, the argument is enclosed in a top-level arrow-function) then null is returned. + *

+ * Note: unlike {@link N4JSASTUtils#getContainingFunctionOrAccessor(EObject)} this method regards + * {@link N4FieldDeclaration} as valid answer (ie, a "nearest enclosing this-binder"). + */ + public static ThisArgProvider nearestEnclosingThisBinder(EObject expr) { + if (expr == null) { + return null; + } + ThisArgProvider enclosingThisBinder = EcoreUtil2.getContainerOfType(expr, ThisArgProvider.class); + if (enclosingThisBinder == null) { + return null; + } + if (isLambda(enclosingThisBinder)) { + // lambda functions provide no binding for 'this', keep searching + return nearestEnclosingThisBinder(enclosingThisBinder.eContainer()); + } + // non-lambda functions do bind 'this'; we've found it + return enclosingThisBinder; + } + + /** + * A top-level lambda has no enclosing lexical context that provides bindings for 'this' and 'arguments'. + */ + public static boolean isTopLevelLambda(FunctionExpression funExpr) { + EObject enclosing = funExpr.eContainer(); + return isLambda(funExpr) && null == nearestEnclosingThisBinder(enclosing); + + } + +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/LambdaUtils.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/LambdaUtils.xtend deleted file mode 100644 index dd00dab2ed..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/LambdaUtils.xtend +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour 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: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.types.utils; - -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.n4JS.ArrowFunction -import org.eclipse.n4js.n4JS.Block -import org.eclipse.n4js.n4JS.FunctionDefinition -import org.eclipse.n4js.n4JS.FunctionExpression -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.N4JSASTUtils -import org.eclipse.n4js.n4JS.ThisArgProvider -import org.eclipse.n4js.n4JS.ThisLiteral -import org.eclipse.n4js.n4JS.ThisTarget -import org.eclipse.n4js.utils.EcoreUtilN4 -import org.eclipse.xtext.EcoreUtil2 - -/** - * Utility methods added to support ES6 arrow functions, aka lambdas. - */ -public class LambdaUtils { - - /** - * All {@link ThisLiteral}s occurring in body that refer to the same this-value; - * ie without delving into functions (unless arrow functions) or into a declaration that introduces - * a this context, ie any {@link ThisTarget} in general. - */ - public static def thisLiterals(Block body) { - EcoreUtilN4.getAllContentsFiltered(body, [!introducesThisContext(it)]).filter(it| it instanceof ThisLiteral) - } - - private static def boolean introducesThisContext(EObject node) { - switch node { - FunctionExpression: !node.isArrowFunction - FunctionDefinition: true - ThisArgProvider: true - default: false - } - } - - public static def boolean isLambda(EObject funDef) { - funDef instanceof ArrowFunction - } - - /** - * A this-binder is a non-lambda {@link FunctionDefinition}. - * This method looks up the nearest such construct that encloses the argument. - * In case the argument itself is a this-binder, it is returned. - * In case no this-binder exists (for example, the argument is enclosed in a top-level arrow-function) then null is returned. - *

- * Note: unlike {@link N4JSASTUtils#getContainingFunctionOrAccessor(EObject)} this method - * regards {@link N4FieldDeclaration} as valid answer (ie, a "nearest enclosing this-binder"). - */ - public static def ThisArgProvider nearestEnclosingThisBinder(EObject expr) { - if (expr === null) { - return null; - } - val enclosingThisBinder = EcoreUtil2.getContainerOfType(expr, ThisArgProvider); - if (enclosingThisBinder === null) { - return null; - } - if (isLambda(enclosingThisBinder)) { - // lambda functions provide no binding for 'this', keep searching - return nearestEnclosingThisBinder(enclosingThisBinder.eContainer); - } - // non-lambda functions do bind 'this'; we've found it - return enclosingThisBinder; - } - - /** - * A top-level lambda has no enclosing lexical context that provides bindings for 'this' and 'arguments'. - */ - public static def isTopLevelLambda(FunctionExpression funExpr) { - isLambda(funExpr) && { - val enclosing = funExpr.eContainer; - null === nearestEnclosingThisBinder(enclosing); - } - } - -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/TypeHelper.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/TypeHelper.java new file mode 100644 index 0000000000..549177f00f --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/TypeHelper.java @@ -0,0 +1,211 @@ +/** + * Copyright (c) 2016 NumberFour 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: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.types.utils; + +import static org.eclipse.n4js.types.utils.SuperTypesList.newSuperTypesList; +import static org.eclipse.n4js.types.utils.TypeUtils.declaredSuperTypes; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; + +import java.util.Iterator; +import java.util.List; + +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.PrimitiveType; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.xtext.xbase.lib.IterableExtensions; + +import com.google.inject.Inject; + +/** + * Helper providing utility methods for type and type ref handling, needs to be injected. Static utility methods can be + * found in {@link TypeUtils}. + */ +public class TypeHelper { + + @Inject + TypeCompareHelper tch; + + /** + * Collects all declared super types of a type referenced by a type references, recognizing cyclic dependencies in + * case of invalid type hierarchies. Type arguments are not considered during comparison, thus G and G will + * result in a single reference in the result. + * + * @param reflexive + * if true, type itself is also added to the list of super types + * + * @return ordered list of super types, using a depth first search, starting with classes, then roles, and + * eventually interfaces. + */ + // TODO functions and other type expressions are not supported yet! + public SuperTypesList collectAllDeclaredSuperTypesTypeargsIgnored(TypeRef ref, boolean reflexive) { + SuperTypesList allDeclaredSuperTypes = newSuperTypesList(tch.getTypeRefComparator()); + + allDeclaredSuperTypes.add(ref); + collectAllDeclaredSuperTypesTypeargsIgnored(ref, allDeclaredSuperTypes); + + // in case of cycles, we do not want the current type to be contained in the super types list + // if reflexive is false + if (!reflexive) { + allDeclaredSuperTypes.remove(ref); + } + return allDeclaredSuperTypes; + } + + /** + * Collects all declared super types of a type referenced by a type references, recognizing cyclic dependencies in + * case of invalid type hierarchies. Type arguments are considered during comparison, thus G and G will both + * be part of the result. + * + * @param reflexive + * if true, type itself is also added to the list of super types + * + * @return ordered list of super types, using a depth first search, starting with classes, then roles, and + * eventually interfaces. + */ + // TODO functions and other type expressions are not supported yet! + public SuperTypesList collectAllDeclaredSuperTypesWithTypeargs(TypeRef ref, boolean reflexive) { + SuperTypesList allDeclaredSuperTypes = newSuperTypesList(tch.getTypeRefComparator()); + + allDeclaredSuperTypes.add(ref); + collectAllDeclaredSuperTypesTypeargsIgnored(ref, allDeclaredSuperTypes); + + // in case of cycles, we do not want the current type to be contained in the super types list + // if reflexive is false + if (!reflexive) { + allDeclaredSuperTypes.remove(ref); + } + return allDeclaredSuperTypes; + } + + /***/ + public SuperTypesList collectAllDeclaredSuperTypes(Type type, boolean reflexive) { + SuperTypesList allDeclaredSuperTypes = newSuperTypesList(tch.getTypeComparator()); + + allDeclaredSuperTypes.add(type); + collectAllDeclaredSuperTypes(type, allDeclaredSuperTypes); + + // in case of cycles, we do not want the current type to be contained in the super types list + // if reflexive is false + if (!reflexive) { + allDeclaredSuperTypes.remove(type); + } + return allDeclaredSuperTypes; + } + + /** + * Collects all type references of given type reference and add these types to both given collections. This method + * requires the tree set to use the {@link TypeCompareHelper#getTypeRefComparator()}. In most cases, you probably + * will call {@link #collectAllDeclaredSuperTypes(Type, boolean)} instead of calling this method directly. However, + * for some optimized methods, it may be useful to call this method directly. + * + * @param allDeclaredSuperTypes + * needs to be instantiated with correct comparator, that is the types are ordered by their qualified + * name + */ + public void collectAllDeclaredSuperTypesTypeargsIgnored(TypeRef ref, + SuperTypesList allDeclaredSuperTypes) { + + if (ref != null && ref.getDeclaredType() != null) { + for (ParameterizedTypeRef superTypeRef : declaredSuperTypes(ref.getDeclaredType())) { + if (allDeclaredSuperTypes.add(superTypeRef)) { + collectAllDeclaredSuperTypesTypeargsIgnored(superTypeRef, allDeclaredSuperTypes); + } + } + } + } + + /***/ + public void collectAllDeclaredSuperTypes(Type type, SuperTypesList allDeclaredSuperTypes) { + if (type != null) { + for (Type superType : map(declaredSuperTypes(type), t -> t.getDeclaredType())) { + if (allDeclaredSuperTypes.add(superType)) { + collectAllDeclaredSuperTypes(superType, allDeclaredSuperTypes); + } + } + } + } + + /** + * Removes the first occurrence of the given type from the iterable, whereby the type is found by name using the + * {@link TypeCompareHelper#getTypeRefComparator()}. The iterator of the iterable needs to support the + * {@link Iterator#remove()} operation. + * + * @return true if type has been found, false otherwise + */ + public boolean removeTypeRef(Iterable typeRefs, TypeRef toBeRemoved) { + Iterator iter = typeRefs.iterator(); + while (iter.hasNext()) { + if (tch.compare(toBeRemoved, iter.next()) == 0) { + iter.remove(); + return true; + } + } + return false; + } + + /** + * Retains all types from list of refs. + *

+ * This method is optimized for leastCommonSuperType and assumes that all types in orderedRefs are ordered as + * returned by collecAllDeclaredSuperTypes(). The iterator returned by typeRefs must support the + * {@link Iterator#remove()} operation. + */ + public void retainAllTypeRefs(Iterable typeRefs, SuperTypesList typesToBeRetained) { + Iterator iter = typeRefs.iterator(); + while (iter.hasNext()) { + if (!typesToBeRetained.contains(iter.next())) { + iter.remove(); + } + } + } + + /** + * Returns true if given iterable contains the type ref, using the {@link TypeCompareHelper#getTypeRefComparator()} + * for finding the type ref. + */ + public boolean containsByType(Iterable typeRefs, TypeRef typeRef) { + return IterableExtensions.exists(typeRefs, tr -> tch.compare(typeRef, tr) == 0); + } + + /** + * Returns the index of the type ref contained in the typeRefs collections, which either equals the given type ref + * by means of the {@link TypeCompareHelper#getTypeRefComparator()}, or which is assignment compatible in case of + * primitive types. + * + * @return index or -1, if type ref has not been found + */ + public int findTypeRefOrAssignmentCompatible(List typeRefs, TypeRef typeRef) { + PrimitiveType assignmentCompatible = (typeRef.getDeclaredType() instanceof PrimitiveType) + ? ((PrimitiveType) typeRef.getDeclaredType()).getAssignmentCompatible() + : null; + int i = 0; + while (i < typeRefs.size()) { + TypeRef t = typeRefs.get(i); + + if (tch.compare(typeRef, t) == 0) { + return i; + } + if (assignmentCompatible != null) { + Type type = t.getDeclaredType(); + if (type instanceof PrimitiveType) { + if (tch.getTypeComparator().compare(assignmentCompatible, type) == 0 || + tch.getTypeComparator().compare(typeRef.getDeclaredType(), + ((PrimitiveType) type).getAssignmentCompatible()) == 0) { + return i; + } + } + } + i = i + 1; + } + return -1; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/TypeHelper.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/TypeHelper.xtend deleted file mode 100644 index 829182fb0b..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/types/utils/TypeHelper.xtend +++ /dev/null @@ -1,194 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour 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: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.types.utils - -import com.google.inject.Inject -import java.util.Iterator -import java.util.List -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.PrimitiveType -import org.eclipse.n4js.ts.types.Type - -import static org.eclipse.n4js.types.utils.SuperTypesList.* - -import static extension org.eclipse.n4js.types.utils.TypeUtils.* - -/** - * Helper providing utility methods for type and type ref handling, needs to be injected. Static utility methods - * can be found in {@link TypeUtils}. - */ -public class TypeHelper { - - @Inject - extension TypeCompareHelper - - /* - * Collects all declared super types of a type referenced by a type references, recognizing cyclic dependencies in - * case of invalid type hierarchies. Type arguments are not considered during comparison, thus G and G - * will result in a single reference in the result. - * - * @param reflexive if true, type itself is also added to the list of super types - * @return ordered list of super types, using a depth first search, starting with classes, then roles, and eventually interfaces. - */ - // TODO functions and other type expressions are not supported yet! - public def SuperTypesList collectAllDeclaredSuperTypesTypeargsIgnored(TypeRef ref, boolean reflexive) { - val allDeclaredSuperTypes = newSuperTypesList(typeRefComparator); - - allDeclaredSuperTypes.add(ref) - ref.collectAllDeclaredSuperTypesTypeargsIgnored(allDeclaredSuperTypes) - - // in case of cycles, we do not want the current type to be contained in the super types list - // if reflexive is false - if (!reflexive) { - allDeclaredSuperTypes.remove(ref); - } - return allDeclaredSuperTypes; - } - - /* - * Collects all declared super types of a type referenced by a type references, recognizing cyclic dependencies in - * case of invalid type hierarchies. Type arguments are considered during comparison, thus G and G - * will both be part of the result. - * - * @param reflexive if true, type itself is also added to the list of super types - * @return ordered list of super types, using a depth first search, starting with classes, then roles, and eventually interfaces. - */ - // TODO functions and other type expressions are not supported yet! - public def SuperTypesList collectAllDeclaredSuperTypesWithTypeargs(TypeRef ref, boolean reflexive) { - val allDeclaredSuperTypes = newSuperTypesList(typeRefComparator); - - allDeclaredSuperTypes.add(ref) - ref.collectAllDeclaredSuperTypesTypeargsIgnored(allDeclaredSuperTypes) - - // in case of cycles, we do not want the current type to be contained in the super types list - // if reflexive is false - if (!reflexive) { - allDeclaredSuperTypes.remove(ref); - } - return allDeclaredSuperTypes; - } - - public def SuperTypesList collectAllDeclaredSuperTypes(Type type, boolean reflexive) { - val allDeclaredSuperTypes = newSuperTypesList(typeComparator); - - allDeclaredSuperTypes.add(type) - type.collectAllDeclaredSuperTypes(allDeclaredSuperTypes) - - // in case of cycles, we do not want the current type to be contained in the super types list - // if reflexive is false - if (!reflexive) { - allDeclaredSuperTypes.remove(type); - } - return allDeclaredSuperTypes; - } - - /** - * Collects all type references of given type reference and add these types to both given collections. This method requires the tree set to use the - * {@link TypeCompareHelper#getTypeRefComparator()}. In most cases, you probably will call {@link #collectAllDeclaredSuperTypes(TypeRef, boolean)} instead of calling this method - * directly. However, for some optimized methods, it may be usefull to call this method directly. - * - * @param allDeclaredSuperTypes - * needs to be instantiated with correct comparator, see {@link collectAllDeclaredSuperTypes(TypeRef)}, that is the types are ordered by their - * qualified name - * @param allDeclaredSuperTypesOrdered ordered list of super types, using a depth first search, starting with classes, then roles, and eventually interfaces. - */ - public def void collectAllDeclaredSuperTypesTypeargsIgnored(TypeRef ref, SuperTypesList allDeclaredSuperTypes) { - if (ref !== null && ref.declaredType !== null) { - for (superTypeRef : ref.declaredType.declaredSuperTypes) { - if (allDeclaredSuperTypes.add(superTypeRef)) { - collectAllDeclaredSuperTypesTypeargsIgnored(superTypeRef, allDeclaredSuperTypes) - } - } - } - } - - public def void collectAllDeclaredSuperTypes(Type type, SuperTypesList allDeclaredSuperTypes) { - if (type !== null) { - for (superType : type.declaredSuperTypes.map[declaredType]) { - if (allDeclaredSuperTypes.add(superType)) { - collectAllDeclaredSuperTypes(superType, allDeclaredSuperTypes) - } - } - } - } - - /** - * Removes the first occurrence of the given type from the iterable, whereby the type is found by name using the - * {@link TypeCompareHelper#getTypeRefComparator()}. The iterator of the iterable needs to support the {@link Iterator#remove()} operation. - * @return true if type has been found, false otherwise - */ - public def boolean removeTypeRef(Iterable typeRefs, TypeRef toBeRemoved) { - val Iterator iter = typeRefs.iterator; - while (iter.hasNext()) { - if (compare(toBeRemoved, iter.next()) === 0) { - iter.remove(); - return true; - } - } - return false; - } - - /** - * Retains all types from list of refs. - *

- * This method is optimized for leastCommonSuperType and - * assumes that all types in orderedRefs are ordered as returned by collecAllDeclaredSuperTypes(). - * The iterator returned by typeRefs must support the {@link Iterator#remove()} operation. - */ - public def void retainAllTypeRefs(Iterable typeRefs, SuperTypesList typesToBeRetained) { - val iter = typeRefs.iterator - while (iter.hasNext) { - if (! typesToBeRetained.contains(iter.next())) { - iter.remove() - } - } - } - - /** - * Returns true if given iterable contains the type ref, using the {@link TypeCompareHelper#getTypeRefComparator()} for finding the type ref. - */ - public def boolean containsByType(Iterable typeRefs, TypeRef typeRef) { - typeRefs.exists[compare(typeRef, it) == 0] - } - - /** - * Returns the index of the type ref contained in the typeRefs collections, which either equals the given type ref - * by means of the {@link TypeCompareHelper#getTypeRefComparator()}, or which is assignment compatible in case of primitive types. - * - * @return index or -1, if type ref has not been found - */ - public def int findTypeRefOrAssignmentCompatible(List typeRefs, TypeRef typeRef) { - val assignmentCompatible = if (typeRef.declaredType instanceof PrimitiveType) { - (typeRef.declaredType as PrimitiveType).assignmentCompatible; - } else { - null; - } - var i = 0; - while (i thisUsages = LambdaUtils.thisLiterals(topLevelLambda.getBody()); + Iterator thisUsages = LambdaUtils.thisLiterals(topLevelLambda.getBody()); while (thisUsages.hasNext()) { EObject thisUsage = thisUsages.next(); addIssue(thisUsage, IssueCodes.KEY_THIS_REJECTED_IN_TOP_LEVEL_LAMBDA);