diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/compileTime/CompileTimeEvaluator.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/compileTime/CompileTimeEvaluator.java new file mode 100644 index 0000000000..bc4edaec4d --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/compileTime/CompileTimeEvaluator.java @@ -0,0 +1,521 @@ +/** + * Copyright (c) 2017 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.compileTime; + +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.symbolObjectType; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filterNull; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.n4js.compileTime.CompileTimeValue.ValueBoolean; +import org.eclipse.n4js.compileTime.CompileTimeValue.ValueInvalid; +import org.eclipse.n4js.compileTime.CompileTimeValue.ValueNumber; +import org.eclipse.n4js.n4JS.AdditiveExpression; +import org.eclipse.n4js.n4JS.BinaryLogicalExpression; +import org.eclipse.n4js.n4JS.BooleanLiteral; +import org.eclipse.n4js.n4JS.ConditionalExpression; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.n4JS.MultiplicativeExpression; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.n4JS.NullLiteral; +import org.eclipse.n4js.n4JS.NumericLiteral; +import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression; +import org.eclipse.n4js.n4JS.ParenExpression; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.n4JS.StringLiteral; +import org.eclipse.n4js.n4JS.TemplateLiteral; +import org.eclipse.n4js.n4JS.TemplateSegment; +import org.eclipse.n4js.n4JS.UnaryExpression; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.postprocessing.ASTMetaInfoCache; +import org.eclipse.n4js.postprocessing.ASTMetaInfoUtils; +import org.eclipse.n4js.postprocessing.ASTProcessor; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.SyntaxRelatedTElement; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TConstableElement; +import org.eclipse.n4js.ts.types.TEnum; +import org.eclipse.n4js.ts.types.TEnumLiteral; +import org.eclipse.n4js.ts.types.TField; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TypesPackage; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.utils.ContainerTypesHelper; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind; +import org.eclipse.n4js.utils.RecursionGuard; +import org.eclipse.n4js.utils.Strings; +import org.eclipse.n4js.validation.N4JSElementKeywordProvider; +import org.eclipse.n4js.validation.validators.N4JSExpressionValidator; +import org.eclipse.xtext.xbase.lib.IterableExtensions; + +import com.google.inject.Inject; + +/** + * Helper class to evaluate compile-time expressions. + *

+ * IMPORTANT IMPLEMENTATION NOTES: + *

+ * + * 1 main rationale for this decision was to keep the handling of computed property names from complicating + * the scoping and main AST traversal. Without resolving computed property names beforehand, all the code in scoping, + * AST traversal, type system, and helper classes such as {@link ContainerTypesHelper} would have to cope with + * unresolved property names, i.e. {@code #getName()} on a property or member would return null or trigger + * some potentially complex computation in the background that might confuse AST traversal. + */ +public class CompileTimeEvaluator { + + @Inject + private N4JSElementKeywordProvider keywordProvider; + + /** + * IMPORTANT: CLIENT CODE SHOULD NOT CALL THIS METHOD!
+ * Instead, read compile-time values from the cache using method + * {@link ASTMetaInfoUtils#getCompileTimeValue(Expression)}.
+ * If the evaluation result of the expression you are interested in is not being cached, add your use case to method + * {@link N4JSLanguageUtils#isProcessedAsCompileTimeExpression(Expression)}. Only expressions for which this method + * returns true will be evaluated and cached during post-processing.
+ *

+ * Computes and returns the value of the given expression as a {@link CompileTimeValue}. If the given expression is + * not a valid compile-time expression, the returned value will be {@link CompileTimeValue#isValid() invalid}. Never + * returns null. + */ + public CompileTimeValue evaluateCompileTimeExpression(RuleEnvironment G, Expression expr) { + return eval(G, expr, new RecursionGuard<>()); + } + + // --------------------------------------------------------------------------------------------------------------- + + // catch-all case for expression not handled by any of the more specific dispatch methods + private CompileTimeValue eval(RuleEnvironment G, Expression expr, RecursionGuard guard) { + if (expr instanceof ParenExpression) { + Expression expression = ((ParenExpression) expr).getExpression(); + if (expression == null) { + return CompileTimeValue.error(); + } + return eval(G, expression, guard); + } + if (expr instanceof NullLiteral) { + return CompileTimeValue.NULL; + } + if (expr instanceof BooleanLiteral) { + return CompileTimeValue.of(((BooleanLiteral) expr).isTrue()); + } + if (expr instanceof NumericLiteral) { + return CompileTimeValue.of(((NumericLiteral) expr).getValue()); + } + if (expr instanceof StringLiteral) { + return CompileTimeValue.of(((StringLiteral) expr).getValue()); + } + if (expr instanceof TemplateSegment) { + return CompileTimeValue.of(((TemplateSegment) expr).getValue()); + } + if (expr instanceof TemplateLiteral) { + return eval(G, (TemplateLiteral) expr, guard); + } + if (expr instanceof UnaryExpression) { + return eval(G, (UnaryExpression) expr, guard); + } + if (expr instanceof AdditiveExpression) { + return eval(G, (AdditiveExpression) expr, guard); + } + if (expr instanceof MultiplicativeExpression) { + return eval(G, (MultiplicativeExpression) expr, guard); + } + if (expr instanceof BinaryLogicalExpression) { + return eval(G, (BinaryLogicalExpression) expr, guard); + } + if (expr instanceof ConditionalExpression) { + return eval(G, (ConditionalExpression) expr, guard); + } + if (expr instanceof IdentifierRef) { + return eval(G, (IdentifierRef) expr, guard); + } + if (expr instanceof ParameterizedPropertyAccessExpression) { + return eval(G, (ParameterizedPropertyAccessExpression) expr, guard); + } + + return CompileTimeValue.error(keywordProvider.keywordWithIndefiniteArticle(expr) + + " is never a compile-time expression", expr); + } + + private CompileTimeValue eval(RuleEnvironment G, TemplateLiteral expr, RecursionGuard guard) { + StringBuilder buff = new StringBuilder(); + List invalidValues = new ArrayList<>(); + for (Expression seg : expr.getSegments()) { + CompileTimeValue segValue = eval(G, seg, guard); + if (segValue.isValid()) { + buff.append(segValue.toString()); + } else { + invalidValues.add(segValue); + } + } + if (!invalidValues.isEmpty()) { + return CompileTimeValue.combineErrors(invalidValues.toArray(new CompileTimeValue[0])); + } + return CompileTimeValue.of(buff.toString()); + } + + private CompileTimeValue eval(RuleEnvironment G, UnaryExpression expr, RecursionGuard guard) { + CompileTimeValue value = (expr.getExpression() != null) ? eval(G, expr.getExpression(), guard) : null; + switch (expr.getOp()) { + case NOT: + return CompileTimeValue.invert(value, expr.getExpression()); + case POS: { + ValueInvalid tmpV = CompileTimeValue.requireValueType(value, ValueNumber.class, "operand must be a number", + expr.getExpression()); + return tmpV != null ? tmpV : value; + } + case NEG: + return CompileTimeValue.negate(value, expr.getExpression()); + case VOID: + return CompileTimeValue.UNDEFINED; + default: + return CompileTimeValue.error("invalid operator: " + expr.getOp(), expr); + } + } + + private CompileTimeValue eval(RuleEnvironment G, AdditiveExpression expr, RecursionGuard guard) { + Expression lhs = expr.getLhs(); + Expression rhs = expr.getRhs(); + CompileTimeValue leftValue = (lhs != null) ? eval(G, lhs, guard) : null; + CompileTimeValue rightValue = (rhs != null) ? eval(G, rhs, guard) : null; + switch (expr.getOp()) { + case ADD: + return CompileTimeValue.add(leftValue, rightValue, expr); + case SUB: + return CompileTimeValue.subtract(leftValue, rightValue, lhs, rhs); + default: + return CompileTimeValue.error("invalid operator: " + expr.getOp(), expr); + } + } + + private CompileTimeValue eval(RuleEnvironment G, MultiplicativeExpression expr, RecursionGuard guard) { + Expression lhs = expr.getLhs(); + Expression rhs = expr.getRhs(); + CompileTimeValue leftValue = (lhs != null) ? eval(G, lhs, guard) : null; + CompileTimeValue rightValue = (rhs != null) ? eval(G, rhs, guard) : null; + switch (expr.getOp()) { + case TIMES: + return CompileTimeValue.multiply(leftValue, rightValue, lhs, rhs); + case DIV: + return CompileTimeValue.divide(leftValue, rightValue, lhs, rhs); + case MOD: + return CompileTimeValue.remainder(leftValue, rightValue, lhs, rhs); + default: + return CompileTimeValue.error("invalid operator: " + expr.getOp(), expr); + } + } + + private CompileTimeValue eval(RuleEnvironment G, BinaryLogicalExpression expr, RecursionGuard guard) { + Expression lhs = expr.getLhs(); + Expression rhs = expr.getRhs(); + CompileTimeValue leftValue = (lhs != null) ? eval(G, lhs, guard) : null; + CompileTimeValue rightValue = (rhs != null) ? eval(G, rhs, guard) : null; + switch (expr.getOp()) { + case AND: + return CompileTimeValue.and(leftValue, rightValue, lhs, rhs); + case OR: + return CompileTimeValue.or(leftValue, rightValue, lhs, rhs); + default: + return CompileTimeValue.error("invalid operator: " + expr.getOp(), expr); + } + } + + private CompileTimeValue eval(RuleEnvironment G, ConditionalExpression expr, RecursionGuard guard) { + Expression condition = expr.getExpression(); + Expression trueExpr = expr.getTrueExpression(); + Expression falseExpr = expr.getFalseExpression(); + CompileTimeValue conditionValue = (condition != null) ? eval(G, condition, guard) : null; + CompileTimeValue trueValue = (trueExpr != null) ? eval(G, trueExpr, guard) : null; + CompileTimeValue falseValue = (falseExpr != null) ? eval(G, falseExpr, guard) : null; + ValueInvalid requireValueType = CompileTimeValue.requireValueType(conditionValue, ValueBoolean.class, + "condition must be a boolean", + expr.getExpression()); + ValueInvalid error = CompileTimeValue.combineErrors(requireValueType, trueValue, falseValue); + if (error != null) { + return error; + } + return (conditionValue != null && ((ValueBoolean) conditionValue).getValue()) ? trueValue : falseValue; + } + + private CompileTimeValue eval(RuleEnvironment G, IdentifierRef expr, RecursionGuard guard) { + if (N4JSLanguageUtils.isUndefinedLiteral(G, expr)) { + return CompileTimeValue.UNDEFINED; + } + IdentifiableElement id = expr.getId(); + // ^^ triggers scoping; this is unproblematic, because the scoping of IdentifierRefs does not + // require type information and will thus not interfere with our goal of handling compile-time expressions and + // computed property names as an up-front preparatory step before main AST traversal. + if (id != null && !id.eIsProxy()) { + return obtainValueIfConstFieldOrVariable(G, id, expr, guard); + } + return CompileTimeValue.error(); + } + + /** + * Handles compile-time evaluation of property access expressions. + *

+ * IMPORTANT IMPLEMENTATION NOTES: + *

+ * YES, this approach introduces an unfortunate duplication of logic, but greatly simplifies other parts of the + * system, i.e. (ordinary) scoping, AST traversal, type system. + */ + private CompileTimeValue eval(RuleEnvironment G, ParameterizedPropertyAccessExpression expr, + RecursionGuard guard) { + Expression targetExpr = expr.getTarget(); + String propName = expr.getPropertyAsText(); // IMPORTANT: don't invoke expr.getProperty()!! + EObject targetElem = null; + if (targetExpr instanceof IdentifierRef) { + if (targetExpr.eIsProxy()) { + return CompileTimeValue.error("Expression is a proxy '" + propName + "'", expr); + } + targetElem = ((IdentifierRef) targetExpr).getId(); + } + EObject sym = symbolObjectType(G); + if (targetElem == sym) { + // A) Is 'expr' an access to a built-in symbol, e.g. Symbol.iterator? + // IMPORTANT: pass in 'false', to disallow proxy resolution (which would trigger scoping, type inference, + // etc.) + TMember memberInSym = N4JSLanguageUtils.getAccessedBuiltInSymbol(G, expr, false); + if (memberInSym != null) { + // yes, it is! + return CompileTimeValue.of(memberInSym); + } else { + return CompileTimeValue.error("Unknown Symbol property '" + propName + "'", expr); + } + } else if (targetElem instanceof TEnum) { + // B) Is 'expr' an access to the literal of a @NumberBased or @StringBased enum? + EnumKind enumKind = N4JSLanguageUtils.getEnumKind((TEnum) targetElem); + if (enumKind != EnumKind.Normal) { + // custom scoping logic! + TEnumLiteral litInEnum = IterableExtensions.findFirst(((TEnum) targetElem).getLiterals(), + l -> Objects.equals(l.getName(), propName)); + if (litInEnum != null) { + // yes, it is! + switch (enumKind) { + case Normal: + throw new IllegalStateException("cannot happen"); + case NumberBased: + return CompileTimeValue.of(litInEnum.getValueNumber()); + case StringBased: + return CompileTimeValue.of(litInEnum.getValueString()); + } + } + } + } else if (targetElem instanceof TClassifier) { + // C) Is 'expr' an access to a const field initialized by a compile-time expression? + // custom scoping logic! + TMember member = findFirst(filterNull(((TClassifier) targetElem).getOwnedMembers()), + m -> Objects.equals(m.getName(), propName) && m.isReadable() && m.isStatic()); + // IMPORTANT: don't use "targetElem.findOwnedMember(memberName, false, true)" in previous line, because + // #findOwnedMember() will create and cache a MemberByNameAndAccessMap, which will be incomplete if the + // TClassifier contains members with unresolved computed property names! + if (member instanceof TField && !((TField) member).isHasComputedName()) { + // yes, it is! + return obtainValueIfConstFieldOrVariable(G, member, expr, guard); + } else { + // we get here in two cases: + // + // 1) member not found + // -> there are a number of possible reasons: + // 1.a) member has a computed property name which was not yet evaluated, + // 1.b) member is inherited, consumed, polyfilled, etc., + // 1.c) member does not exist at all. + // At this point, i.e. before computed names are processed, we cannot distinguish between these + // cases. So we create a dummy error here that will be improved later. + // + // 2) member was found but it has a (resolved) computed property name (since processing of computed + // property names for the current resource has not started yet, this happens only if the member is + // located in another file an its full type information, including its name, was found in the index) + // -> for consistency with 1.a above, we have to raise an error also in this case + return CompileTimeValue.error(new UnresolvedPropertyAccessError(expr)); + } + } + // D) all other cases: + if (targetElem != sym && !(targetElem instanceof TClassifier || targetElem instanceof TEnum)) { + return CompileTimeValue.error( + "target of a property access must be a direct reference to a class, interface, or enum", expr, + N4JSPackage.Literals.EXPRESSION_WITH_TARGET__TARGET); + } + return CompileTimeValue.error( + "property access must point to const fields, literals of @NumberBased/@StringBased enums, or built-in symbols", + expr); + } + + // --------------------------------------------------------------------------------------------------------------- + + /** + * Iff the given element is a const field or variable with a valid compile-time expression as initializer, then this + * method returns its compile-time value; otherwise, an invalid compile-time value with an appropriate error message + * is returned. Never returns null. + *

+ * This method only handles infinite recursion; main logic in + * {@link #obtainValueIfConstFieldOrVariableUnguarded(RuleEnvironment, IdentifiableElement, EObject, RecursionGuard)}. + */ + private CompileTimeValue obtainValueIfConstFieldOrVariable(RuleEnvironment G, IdentifiableElement targetElem, + EObject astNodeForErrorMessage, RecursionGuard guard) { + + if (guard.tryNext(targetElem)) { + try { + return obtainValueIfConstFieldOrVariableUnguarded(G, targetElem, astNodeForErrorMessage, guard); + } finally { + guard.done(targetElem); + } + } else { + return CompileTimeValue.error("cyclic definition of compile-time expression", astNodeForErrorMessage); + } + } + + private CompileTimeValue obtainValueIfConstFieldOrVariableUnguarded(RuleEnvironment G, + IdentifiableElement targetElem, EObject astNodeForErrorMessage, RecursionGuard guard) { + + boolean targetElemIsConst = false; + if (targetElem instanceof TConstableElement) { + targetElemIsConst = ((TConstableElement) targetElem).isConst(); + } + if (targetElem instanceof N4FieldDeclaration) { + targetElemIsConst = ((N4FieldDeclaration) targetElem).isConst(); + } + if (targetElem instanceof VariableDeclaration) { + targetElemIsConst = ((VariableDeclaration) targetElem).isConst(); + } + if (!targetElemIsConst) { + return CompileTimeValue.error( + keywordProvider.keyword(targetElem) + " " + targetElem.getName() + " is not const", + astNodeForErrorMessage); + } + + CompileTimeValue valueOfTargetElem = obtainCompileTimeValueOfTargetElement(G, + astNodeForErrorMessage.eResource(), targetElem, guard); + if (valueOfTargetElem != null) { + if (valueOfTargetElem instanceof ValueInvalid) { + String baseMsg = keywordProvider.keyword(targetElem) + " " + targetElem.getName() + + " is const but does not have a compile-time expression as initializer"; + String msg = combineErrorMessageWithNestedErrors(baseMsg, + ((ValueInvalid) valueOfTargetElem).getErrors().toArray(new CompileTimeEvaluationError[0])); + EReference feature = null; + if (astNodeForErrorMessage instanceof ParameterizedPropertyAccessExpression) { + feature = N4JSPackage.eINSTANCE.getParameterizedPropertyAccessExpression_Property(); + } + return CompileTimeValue.error(msg, astNodeForErrorMessage, feature); + } + return valueOfTargetElem; + } + return CompileTimeValue.error( + "only references to const variables with a compile-time expression as initializer are allowed", + astNodeForErrorMessage); + } + + private CompileTimeValue obtainCompileTimeValueOfTargetElement(RuleEnvironment G, Resource currentResource, + IdentifiableElement targetElem, RecursionGuard guard) { + + if (targetElem.eResource() == currentResource || hasLoadedASTElement(targetElem)) { + // 'targetElem' is in same resource OR is in a different resource that already has a fully-loaded AST + // -> compute value from the initializer expression of 'targetElem' + EObject astNodeOfTargetElem = (targetElem instanceof SyntaxRelatedTElement) + ? ((SyntaxRelatedTElement) targetElem).getAstElement() + // NOTE: this will never trigger demand-loading of an AST, because above we + // ensured that we are still in 'currentResource' OR method #hasLoadedASTElement() has returned true + : targetElem // here we simply assume that elem is already an AST node + ; + Expression expressionOfTargetElem = null; + if (astNodeOfTargetElem instanceof N4FieldDeclaration) { + expressionOfTargetElem = ((N4FieldDeclaration) astNodeOfTargetElem).getExpression(); + } + if (astNodeOfTargetElem instanceof VariableDeclaration) { + expressionOfTargetElem = ((VariableDeclaration) astNodeOfTargetElem).getExpression(); + } + if (expressionOfTargetElem != null) { + return eval(G, expressionOfTargetElem, guard); + } + } else { + // 'targetElem' is in another resource with an AST proxy + // -> read value from TModule to avoid demand-loading of AST + if (targetElem instanceof TConstableElement) { + return CompileTimeValue.deserialize(((TConstableElement) targetElem).getCompileTimeValue()); + } + } + return null; // no value found + } + + /** + * Tells if given element has an AST element in an already loaded AST (i.e. it is safe to invoke method + * {@code #getASTElement()} without triggering a demand-load of the AST). + */ + private static boolean hasLoadedASTElement(IdentifiableElement elem) { + EObject astElemNonResolved = null; + if (elem instanceof SyntaxRelatedTElement) { + astElemNonResolved = (EObject) elem.eGet(TypesPackage.eINSTANCE.getSyntaxRelatedTElement_AstElement(), + false); + } + return astElemNonResolved != null && !astElemNonResolved.eIsProxy(); + } + + private static String combineErrorMessageWithNestedErrors(String mainMessage, + CompileTimeEvaluationError... nestedErrors) { + + if (nestedErrors.length == 0) { + return mainMessage; + } else if (nestedErrors.length == 1) { + return mainMessage + ": " + nestedErrors[0].getMessageWithLocation(); + } else { + return mainMessage + ":\n- " + + Strings.join("\n- ", map(List.of(nestedErrors), e -> e.getMessageWithLocation())); + } + } + + // --------------------------------------------------------------------------------------------------------------- + + /** + * Special kind of {@link CompileTimeEvaluationError} used to denote a particular case in which the + * {@link CompileTimeEvaluator} cannot come up with the correct error message and thus delegates finding a proper + * message to the validation, i.e. to class {@link N4JSExpressionValidator}. + */ + public static final class UnresolvedPropertyAccessError extends CompileTimeEvaluationError { + + /***/ + public UnresolvedPropertyAccessError(ParameterizedPropertyAccessExpression astNode) { + super("*** UnresolvedPropertyAccessError ***", astNode, + N4JSPackage.eINSTANCE.getParameterizedPropertyAccessExpression_Property()); + } + + /***/ + public ParameterizedPropertyAccessExpression getAstNodeCasted() { + return (ParameterizedPropertyAccessExpression) astNode; + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/compileTime/CompileTimeEvaluator.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/compileTime/CompileTimeEvaluator.xtend deleted file mode 100644 index 0f4ebefe9d..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/compileTime/CompileTimeEvaluator.xtend +++ /dev/null @@ -1,447 +0,0 @@ -/** - * Copyright (c) 2017 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.compileTime - -import com.google.inject.Inject -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.n4js.compileTime.CompileTimeValue.ValueBoolean -import org.eclipse.n4js.compileTime.CompileTimeValue.ValueInvalid -import org.eclipse.n4js.compileTime.CompileTimeValue.ValueNumber -import org.eclipse.n4js.n4JS.AdditiveExpression -import org.eclipse.n4js.n4JS.BinaryLogicalExpression -import org.eclipse.n4js.n4JS.BooleanLiteral -import org.eclipse.n4js.n4JS.ConditionalExpression -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.IdentifierRef -import org.eclipse.n4js.n4JS.MultiplicativeExpression -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.N4JSPackage -import org.eclipse.n4js.n4JS.NullLiteral -import org.eclipse.n4js.n4JS.NumericLiteral -import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression -import org.eclipse.n4js.n4JS.ParenExpression -import org.eclipse.n4js.n4JS.StringLiteral -import org.eclipse.n4js.n4JS.TemplateLiteral -import org.eclipse.n4js.n4JS.TemplateSegment -import org.eclipse.n4js.n4JS.UnaryExpression -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.postprocessing.ASTMetaInfoUtils -import org.eclipse.n4js.postprocessing.ASTProcessor -import org.eclipse.n4js.ts.types.IdentifiableElement -import org.eclipse.n4js.ts.types.SyntaxRelatedTElement -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TConstableElement -import org.eclipse.n4js.ts.types.TEnum -import org.eclipse.n4js.ts.types.TField -import org.eclipse.n4js.ts.types.TypesPackage -import org.eclipse.n4js.typesystem.utils.RuleEnvironment -import org.eclipse.n4js.utils.ContainerTypesHelper -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind -import org.eclipse.n4js.utils.RecursionGuard -import org.eclipse.n4js.validation.N4JSElementKeywordProvider -import org.eclipse.n4js.validation.validators.N4JSExpressionValidator - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * Helper class to evaluate compile-time expressions. - *

- * IMPORTANT IMPLEMENTATION NOTES: - *

- * - * 1 main rationale for this decision was to keep the handling of computed property names from complicating - * the scoping and main AST traversal. Without resolving computed property names beforehand, all the code in scoping, - * AST traversal, type system, and helper classes such as {@link ContainerTypesHelper} would have to cope with - * unresolved property names, i.e. {@code #getName()} on a property or member would return null or trigger - * some potentially complex computation in the background that might confuse AST traversal. - */ -class CompileTimeEvaluator { - - @Inject - private N4JSElementKeywordProvider keywordProvider; - - - /** - * - * IMPORTANT: CLIENT CODE SHOULD NOT CALL THIS METHOD!
- * Instead, read compile-time values from the cache using method {@link ASTMetaInfoUtils#getCompileTimeValue(Expression)}.
- * If the evaluation result of the expression you are interested in is not being cached, add your use case to method - * {@link N4JSLanguageUtils#isProcessedAsCompileTimeExpression(Expression)}. Only expressions for which this method - * returns true will be evaluated and cached during post-processing. - *
- *

- * Computes and returns the value of the given expression as a {@link CompileTimeValue}. If the given expression is - * not a valid compile-time expression, the returned value will be {@link CompileTimeValue#isValid() invalid}. Never - * returns null. - */ - def public CompileTimeValue evaluateCompileTimeExpression(RuleEnvironment G, Expression expr) { - return eval(G, expr, new RecursionGuard()); - } - - - // --------------------------------------------------------------------------------------------------------------- - - - // catch-all case for expression not handled by any of the more specific dispatch methods - def private dispatch CompileTimeValue eval(RuleEnvironment G, Expression expr, RecursionGuard guard) { - return CompileTimeValue.error(keywordProvider.keywordWithIndefiniteArticle(expr) - + " is never a compile-time expression", expr); - } - - def private dispatch CompileTimeValue eval(RuleEnvironment G, ParenExpression expr, RecursionGuard guard) { - if (expr.expression === null) - return CompileTimeValue.error(); - return eval(G, expr.expression, guard); - } - - def private dispatch CompileTimeValue eval(RuleEnvironment G, NullLiteral expr, RecursionGuard guard) { - return CompileTimeValue.NULL; - } - - def private dispatch CompileTimeValue eval(RuleEnvironment G, BooleanLiteral expr, RecursionGuard guard) { - return CompileTimeValue.of(expr.isTrue); - } - - def private dispatch CompileTimeValue eval(RuleEnvironment G, NumericLiteral expr, RecursionGuard guard) { - return CompileTimeValue.of(expr.value); - } - - def private dispatch CompileTimeValue eval(RuleEnvironment G, StringLiteral expr, RecursionGuard guard) { - return CompileTimeValue.of(expr.value); - } - - def private dispatch CompileTimeValue eval(RuleEnvironment G, TemplateSegment expr, RecursionGuard guard) { - return CompileTimeValue.of(expr.value); - } - - def private dispatch CompileTimeValue eval(RuleEnvironment G, TemplateLiteral expr, RecursionGuard guard) { - val buff = new StringBuilder; - val invalidValues = newArrayList; - for (seg : expr.segments) { - val segValue = eval(G, seg, guard); - if (segValue.valid) { - buff.append(segValue.toString); - } else { - invalidValues += segValue; - } - } - if (!invalidValues.empty) { - return CompileTimeValue.combineErrors(invalidValues); - } - return CompileTimeValue.of(buff.toString); - } - - def private dispatch CompileTimeValue eval(RuleEnvironment G, UnaryExpression expr, RecursionGuard guard) { - val value = if (expr.expression !== null) eval(G, expr.expression, guard); - return switch (expr.op) { - case NOT: CompileTimeValue.invert(value, expr.expression) - case POS: CompileTimeValue.requireValueType(value, ValueNumber, "operand must be a number", expr.expression) ?: value - case NEG: CompileTimeValue.negate(value, expr.expression) - case VOID: CompileTimeValue.UNDEFINED - default: CompileTimeValue.error("invalid operator: " + expr.op, expr) - }; - } - - def private dispatch CompileTimeValue eval(RuleEnvironment G, AdditiveExpression expr, RecursionGuard guard) { - val lhs = expr.lhs; - val rhs = expr.rhs; - val leftValue = if (lhs !== null) eval(G, lhs, guard); - val rightValue = if (rhs !== null) eval(G, rhs, guard); - return switch (expr.op) { - case ADD: CompileTimeValue.add(leftValue, rightValue, expr) - case SUB: CompileTimeValue.subtract(leftValue, rightValue, lhs, rhs) - default: CompileTimeValue.error("invalid operator: " + expr.op, expr) - }; - } - - def private dispatch CompileTimeValue eval(RuleEnvironment G, MultiplicativeExpression expr, RecursionGuard guard) { - val lhs = expr.lhs; - val rhs = expr.rhs; - val leftValue = if (lhs !== null) eval(G, lhs, guard); - val rightValue = if (rhs !== null) eval(G, rhs, guard); - return switch (expr.op) { - case TIMES: CompileTimeValue.multiply(leftValue, rightValue, lhs, rhs) - case DIV: CompileTimeValue.divide(leftValue, rightValue, lhs, rhs) - case MOD: CompileTimeValue.remainder(leftValue, rightValue, lhs, rhs) - default: CompileTimeValue.error("invalid operator: " + expr.op, expr) - }; - } - - def private dispatch CompileTimeValue eval(RuleEnvironment G, BinaryLogicalExpression expr, RecursionGuard guard) { - val lhs = expr.lhs; - val rhs = expr.rhs; - val leftValue = if (lhs !== null) eval(G, lhs, guard); - val rightValue = if (rhs !== null) eval(G, rhs, guard); - return switch (expr.op) { - case AND: CompileTimeValue.and(leftValue, rightValue, lhs, rhs) - case OR: CompileTimeValue.or(leftValue, rightValue, lhs, rhs) - default: CompileTimeValue.error("invalid operator: " + expr.op, expr) - }; - } - - def private dispatch CompileTimeValue eval(RuleEnvironment G, ConditionalExpression expr, RecursionGuard guard) { - val condition = expr.expression; - val trueExpr = expr.trueExpression; - val falseExpr = expr.falseExpression; - val conditionValue = if (condition !== null) eval(G, condition, guard); - val trueValue = if (trueExpr !== null) eval(G, trueExpr, guard); - val falseValue = if (falseExpr !== null) eval(G, falseExpr, guard); - val error = CompileTimeValue.combineErrors( - CompileTimeValue.requireValueType(conditionValue, ValueBoolean, "condition must be a boolean", - expr.expression), trueValue, falseValue); - if (error !== null) { - return error; - } - return if ((conditionValue as ValueBoolean).getValue()) trueValue else falseValue; - } - - def private dispatch CompileTimeValue eval(RuleEnvironment G, IdentifierRef expr, RecursionGuard guard) { - if (N4JSLanguageUtils.isUndefinedLiteral(G, expr)) { - return CompileTimeValue.UNDEFINED; - } - val id = expr.id; // <-- triggers scoping; this is unproblematic, because the scoping of IdentifierRefs does not - // require type information and will thus not interfere with our goal of handling compile-time expressions and - // computed property names as an up-front preparatory step before main AST traversal. - if (id !== null && !id.eIsProxy) { - return obtainValueIfConstFieldOrVariable(G, id, expr, guard); - } - return CompileTimeValue.error(); - } - - /** - * Handles compile-time evaluation of property access expressions. - *

- * IMPORTANT IMPLEMENTATION NOTES: - *

- * YES, this approach introduces an unfortunate duplication of logic, but greatly simplifies other parts of the - * system, i.e. (ordinary) scoping, AST traversal, type system. - */ - def private dispatch CompileTimeValue eval(RuleEnvironment G, ParameterizedPropertyAccessExpression expr, RecursionGuard guard) { - val targetExpr = expr.target; - val propName = expr.propertyAsText; // IMPORTANT: don't invoke expr.getProperty()!! - val targetElem = if (targetExpr instanceof IdentifierRef) { - if (targetExpr.eIsProxy) { - return CompileTimeValue.error("Expression is a proxy '"+ propName +"'", expr); - } - targetExpr.id; - } - val sym = G.symbolObjectType; - if (targetElem === sym) { - // A) Is 'expr' an access to a built-in symbol, e.g. Symbol.iterator? - val memberInSym = N4JSLanguageUtils.getAccessedBuiltInSymbol(G, expr, false); // IMPORTANT: pass in 'false', to disallow proxy resolution (which would trigger scoping, type inference, etc.) - if (memberInSym !== null) { - // yes, it is! - return CompileTimeValue.of(memberInSym); - } else { - return CompileTimeValue.error("Unknown Symbol property '"+ propName +"'", expr); - } - } else if (targetElem instanceof TEnum) { - // B) Is 'expr' an access to the literal of a @NumberBased or @StringBased enum? - val enumKind = N4JSLanguageUtils.getEnumKind(targetElem); - if (enumKind !== EnumKind.Normal) { - val litInEnum = targetElem.literals.findFirst[name == propName]; // custom scoping logic! - if (litInEnum !== null) { - // yes, it is! - return switch (enumKind) { - case Normal: - throw new IllegalStateException("cannot happen") - case NumberBased: - CompileTimeValue.of(litInEnum.valueNumber) - case StringBased: - CompileTimeValue.of(litInEnum.valueString) - }; - } - } - } else if (targetElem instanceof TClassifier) { - // C) Is 'expr' an access to a const field initialized by a compile-time expression? - val member = targetElem.ownedMembers.filterNull.findFirst[name == propName && readable && static]; // custom scoping logic! - // IMPORTANT: don't use "targetElem.findOwnedMember(memberName, false, true)" in previous line, because - // #findOwnedMember() will create and cache a MemberByNameAndAccessMap, which will be incomplete if the - // TClassifier contains members with unresolved computed property names! - if (member instanceof TField && !(member as TField).hasComputedName) { - // yes, it is! - return obtainValueIfConstFieldOrVariable(G, member, expr, guard); - } else { - // we get here in two cases: - // - // 1) member not found - // -> there are a number of possible reasons: - // 1.a) member has a computed property name which was not yet evaluated, - // 1.b) member is inherited, consumed, polyfilled, etc., - // 1.c) member does not exist at all. - // At this point, i.e. before computed names are processed, we cannot distinguish between these - // cases. So we create a dummy error here that will be improved later. - // - // 2) member was found but it has a (resolved) computed property name (since processing of computed - // property names for the current resource has not started yet, this happens only if the member is - // located in another file an its full type information, including its name, was found in the index) - // -> for consistency with 1.a above, we have to raise an error also in this case - return CompileTimeValue.error(new UnresolvedPropertyAccessError(expr)); - } - } - // D) all other cases: - if (targetElem !== sym && !(targetElem instanceof TClassifier || targetElem instanceof TEnum)) { - return CompileTimeValue.error( - "target of a property access must be a direct reference to a class, interface, or enum", expr, - N4JSPackage.Literals.EXPRESSION_WITH_TARGET__TARGET); - } - return CompileTimeValue.error("property access must point to const fields, literals of @NumberBased/@StringBased enums, or built-in symbols", expr); - } - - - // --------------------------------------------------------------------------------------------------------------- - - - /** - * Iff the given element is a const field or variable with a valid compile-time expression as initializer, then this - * method returns its compile-time value; otherwise, an invalid compile-time value with an appropriate error message - * is returned. Never returns null. - *

- * This method only handles infinite recursion; main logic in - * {@link #obtainValueIfConstFieldOrVariableUnguarded(RuleEnvironment, IdentifiableElement, EObject, RecursionGuard)}. - */ - def private CompileTimeValue obtainValueIfConstFieldOrVariable(RuleEnvironment G, IdentifiableElement targetElem, - EObject astNodeForErrorMessage, RecursionGuard guard) { - - if (guard.tryNext(targetElem)) { - try { - return obtainValueIfConstFieldOrVariableUnguarded(G, targetElem, astNodeForErrorMessage, guard); - } finally { - guard.done(targetElem); - } - } else { - return CompileTimeValue.error("cyclic definition of compile-time expression", astNodeForErrorMessage); - } - } - - def private CompileTimeValue obtainValueIfConstFieldOrVariableUnguarded(RuleEnvironment G, - IdentifiableElement targetElem, EObject astNodeForErrorMessage, RecursionGuard guard) { - - val targetElemIsConst = switch (targetElem) { - TConstableElement: targetElem.const - N4FieldDeclaration: targetElem.const - VariableDeclaration: targetElem.const - }; - if (!targetElemIsConst) { - return CompileTimeValue.error( - keywordProvider.keyword(targetElem) + " " + targetElem.name + " is not const", - astNodeForErrorMessage); - } - - val valueOfTargetElem = obtainCompileTimeValueOfTargetElement(G, astNodeForErrorMessage.eResource, targetElem, guard); - if (valueOfTargetElem !== null) { - if (valueOfTargetElem instanceof ValueInvalid) { - val baseMsg = keywordProvider.keyword(targetElem) + " " + targetElem.name + - " is const but does not have a compile-time expression as initializer"; - val msg = combineErrorMessageWithNestedErrors(baseMsg, valueOfTargetElem.errors); - val feature = if (astNodeForErrorMessage instanceof ParameterizedPropertyAccessExpression) { - N4JSPackage.eINSTANCE.parameterizedPropertyAccessExpression_Property - }; - return CompileTimeValue.error(msg, astNodeForErrorMessage, feature); - } - return valueOfTargetElem; - } - return CompileTimeValue.error( - "only references to const variables with a compile-time expression as initializer are allowed", - astNodeForErrorMessage); - } - - def private CompileTimeValue obtainCompileTimeValueOfTargetElement(RuleEnvironment G, Resource currentResource, - IdentifiableElement targetElem, RecursionGuard guard) { - - if (targetElem.eResource === currentResource || hasLoadedASTElement(targetElem)) { - // 'targetElem' is in same resource OR is in a different resource that already has a fully-loaded AST - // -> compute value from the initializer expression of 'targetElem' - val astNodeOfTargetElem = if (targetElem instanceof SyntaxRelatedTElement) { - targetElem.astElement // NOTE: this will never trigger demand-loading of an AST, because above we - // ensured that we are still in 'currentResource' OR method #hasLoadedASTElement() has returned true - } else { - targetElem // here we simply assume that elem is already an AST node - }; - val expressionOfTargetElem = switch (astNodeOfTargetElem) { - N4FieldDeclaration: astNodeOfTargetElem.expression - VariableDeclaration: astNodeOfTargetElem.expression - }; - if (expressionOfTargetElem !== null) { - return eval(G, expressionOfTargetElem, guard); - } - } else { - // 'targetElem' is in another resource with an AST proxy - // -> read value from TModule to avoid demand-loading of AST - if (targetElem instanceof TConstableElement) { - return CompileTimeValue.deserialize(targetElem.compileTimeValue); - } - } - return null; // no value found - } - - /** - * Tells if given element has an AST element in an already loaded AST (i.e. it is safe to invoke method - * {@code #getASTElement()} without triggering a demand-load of the AST). - */ - def private static boolean hasLoadedASTElement(IdentifiableElement elem) { - val astElemNonResolved = if (elem instanceof SyntaxRelatedTElement) { - elem.eGet(TypesPackage.eINSTANCE.syntaxRelatedTElement_AstElement, false) as EObject - }; - return astElemNonResolved !== null && !astElemNonResolved.eIsProxy; - } - - def private static String combineErrorMessageWithNestedErrors(String mainMessage, - CompileTimeEvaluationError... nestedErrors) { - - if (nestedErrors.length == 0) { - return mainMessage; - } else if (nestedErrors.length == 1) { - return mainMessage + ": " + nestedErrors.get(0).messageWithLocation; - } else { - return mainMessage + ":\n- " + nestedErrors.map[messageWithLocation].join("\n- "); - } - } - - - // --------------------------------------------------------------------------------------------------------------- - - - /** - * Special kind of {@link CompileTimeEvaluationError} used to denote a particular case in which the - * {@link CompileTimeEvaluator} cannot come up with the correct error message and thus delegates finding a proper - * message to the validation, i.e. to class {@link N4JSExpressionValidator}. - */ - public static final class UnresolvedPropertyAccessError extends CompileTimeEvaluationError { - - public new(ParameterizedPropertyAccessExpression astNode) { - super("*** UnresolvedPropertyAccessError ***", astNode, - N4JSPackage.eINSTANCE.getParameterizedPropertyAccessExpression_Property()); - } - - def public ParameterizedPropertyAccessExpression getAstNodeCasted() { - return astNode as ParameterizedPropertyAccessExpression; - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.java new file mode 100644 index 0000000000..b2dc12c9b1 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.java @@ -0,0 +1,2379 @@ +/** + * 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.formatting2; + +import static org.eclipse.n4js.formatting2.N4JSFormatterPreferenceKeys.FORMAT_AUTO_WRAP_IN_FRONT_OF_LOGICAL_OPERATOR; +import static org.eclipse.n4js.formatting2.N4JSFormatterPreferenceKeys.FORMAT_MAX_CONSECUTIVE_NEWLINES; +import static org.eclipse.n4js.formatting2.N4JSFormatterPreferenceKeys.FORMAT_PARENTHESIS; +import static org.eclipse.n4js.formatting2.N4JSFormatterPreferenceKeys.FORMAT_SURROUND_IMPORT_LIST_WITH_SPACE; +import static org.eclipse.n4js.formatting2.N4JSGenericFormatter.PRIO_3; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.head; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.last; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.tail; + +import java.util.function.Function; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.formatting2.N4JSGenericFormatter.IndentHandlingTextReplaceMerger; +import org.eclipse.n4js.n4JS.AbstractAnnotationList; +import org.eclipse.n4js.n4JS.AbstractCaseClause; +import org.eclipse.n4js.n4JS.AdditiveExpression; +import org.eclipse.n4js.n4JS.AnnotableExpression; +import org.eclipse.n4js.n4JS.AnnotableN4MemberDeclaration; +import org.eclipse.n4js.n4JS.AnnotablePropertyAssignment; +import org.eclipse.n4js.n4JS.AnnotableScriptElement; +import org.eclipse.n4js.n4JS.Annotation; +import org.eclipse.n4js.n4JS.AnnotationList; +import org.eclipse.n4js.n4JS.Argument; +import org.eclipse.n4js.n4JS.ArrayElement; +import org.eclipse.n4js.n4JS.ArrayLiteral; +import org.eclipse.n4js.n4JS.ArrowFunction; +import org.eclipse.n4js.n4JS.AssignmentExpression; +import org.eclipse.n4js.n4JS.AwaitExpression; +import org.eclipse.n4js.n4JS.BinaryBitwiseExpression; +import org.eclipse.n4js.n4JS.BinaryLogicalExpression; +import org.eclipse.n4js.n4JS.BindingPattern; +import org.eclipse.n4js.n4JS.Block; +import org.eclipse.n4js.n4JS.BooleanLiteral; +import org.eclipse.n4js.n4JS.CastExpression; +import org.eclipse.n4js.n4JS.CatchBlock; +import org.eclipse.n4js.n4JS.CommaExpression; +import org.eclipse.n4js.n4JS.ConditionalExpression; +import org.eclipse.n4js.n4JS.EqualityExpression; +import org.eclipse.n4js.n4JS.ExportDeclaration; +import org.eclipse.n4js.n4JS.ExportableElement; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.ExpressionStatement; +import org.eclipse.n4js.n4JS.FieldAccessor; +import org.eclipse.n4js.n4JS.FinallyBlock; +import org.eclipse.n4js.n4JS.ForStatement; +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.n4JS.FunctionDeclaration; +import org.eclipse.n4js.n4JS.FunctionDefinition; +import org.eclipse.n4js.n4JS.FunctionExpression; +import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor; +import org.eclipse.n4js.n4JS.GenericDeclaration; +import org.eclipse.n4js.n4JS.GetterDeclaration; +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.n4JS.IfStatement; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.IndexedAccessExpression; +import org.eclipse.n4js.n4JS.IntLiteral; +import org.eclipse.n4js.n4JS.JSXElement; +import org.eclipse.n4js.n4JS.MultiplicativeExpression; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.N4EnumDeclaration; +import org.eclipse.n4js.n4JS.N4EnumLiteral; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4InterfaceDeclaration; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.n4JS.N4MemberDeclaration; +import org.eclipse.n4js.n4JS.N4SetterDeclaration; +import org.eclipse.n4js.n4JS.N4TypeVariable; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.NewExpression; +import org.eclipse.n4js.n4JS.NullLiteral; +import org.eclipse.n4js.n4JS.ObjectLiteral; +import org.eclipse.n4js.n4JS.ParameterizedCallExpression; +import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression; +import org.eclipse.n4js.n4JS.ParenExpression; +import org.eclipse.n4js.n4JS.PostfixExpression; +import org.eclipse.n4js.n4JS.PromisifyExpression; +import org.eclipse.n4js.n4JS.PropertyAssignment; +import org.eclipse.n4js.n4JS.RegularExpressionLiteral; +import org.eclipse.n4js.n4JS.RelationalExpression; +import org.eclipse.n4js.n4JS.ReturnStatement; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.n4JS.ScriptElement; +import org.eclipse.n4js.n4JS.ShiftExpression; +import org.eclipse.n4js.n4JS.Statement; +import org.eclipse.n4js.n4JS.StringLiteral; +import org.eclipse.n4js.n4JS.SuperLiteral; +import org.eclipse.n4js.n4JS.SwitchStatement; +import org.eclipse.n4js.n4JS.TaggedTemplateString; +import org.eclipse.n4js.n4JS.TemplateLiteral; +import org.eclipse.n4js.n4JS.TemplateSegment; +import org.eclipse.n4js.n4JS.ThisLiteral; +import org.eclipse.n4js.n4JS.ThrowStatement; +import org.eclipse.n4js.n4JS.TypeReferenceNode; +import org.eclipse.n4js.n4JS.UnaryExpression; +import org.eclipse.n4js.n4JS.UnaryOperator; +import org.eclipse.n4js.n4JS.VariableBinding; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.VariableDeclarationOrBinding; +import org.eclipse.n4js.n4JS.VariableStatement; +import org.eclipse.n4js.n4JS.YieldExpression; +import org.eclipse.n4js.services.N4JSGrammarAccess; +import org.eclipse.n4js.ts.typeRefs.IntersectionTypeExpression; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.StaticBaseTypeRef; +import org.eclipse.n4js.ts.typeRefs.StructuralTypeRef; +import org.eclipse.n4js.ts.typeRefs.ThisTypeRef; +import org.eclipse.n4js.ts.typeRefs.ThisTypeRefStructural; +import org.eclipse.n4js.ts.typeRefs.TypeArgument; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRefsPackage; +import org.eclipse.n4js.ts.typeRefs.UnionTypeExpression; +import org.eclipse.n4js.ts.types.TField; +import org.eclipse.n4js.ts.types.TGetter; +import org.eclipse.n4js.ts.types.TStructMember; +import org.eclipse.n4js.ts.types.TypesPackage; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.Keyword; +import org.eclipse.xtext.formatting2.IAutowrapFormatter; +import org.eclipse.xtext.formatting2.IFormattableDocument; +import org.eclipse.xtext.formatting2.IHiddenRegionFormatter; +import org.eclipse.xtext.formatting2.IHiddenRegionFormatting; +import org.eclipse.xtext.formatting2.ITextReplacer; +import org.eclipse.xtext.formatting2.internal.SinglelineCodeCommentReplacer; +import org.eclipse.xtext.formatting2.internal.SinglelineDocCommentReplacer; +import org.eclipse.xtext.formatting2.regionaccess.IComment; +import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion; +import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion; +import org.eclipse.xtext.formatting2.regionaccess.ILineRegion; +import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion; +import org.eclipse.xtext.formatting2.regionaccess.ISequentialRegion; +import org.eclipse.xtext.formatting2.regionaccess.ITextSegment; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.xbase.lib.Pair; +import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; +import org.eclipse.xtext.xtext.generator.parser.antlr.splitting.simpleExpressions.NumberLiteral; + +import com.google.inject.Inject; + +/***/ +@SuppressWarnings("restriction") +public class N4JSFormatter extends TypeExpressionsFormatter { + private final static Logger LOGGER = Logger.getLogger(TypeExpressionsFormatter.class); + + /** Debug switch */ + private static boolean debug = false; + + @Inject + N4JSGrammarAccess grammarAccess; + + /** + * PRIO_4 = -7 - still very low. Standard priorities in the formatter are: + *

+ */ + static int PRIO_4 = PRIO_3 + 1; + static int PRIO_13 = IHiddenRegionFormatter.HIGH_PRIORITY + 1; + + @Override + public void format(Object clazz, IFormattableDocument document) { + if (clazz instanceof N4ClassDeclaration) { + _format((N4ClassDeclaration) clazz, document); + return; + } else if (clazz instanceof N4InterfaceDeclaration) { + _format((N4InterfaceDeclaration) clazz, document); + return; + } else if (clazz instanceof ArrowFunction) { + _format((ArrowFunction) clazz, document); + return; + } else if (clazz instanceof N4EnumDeclaration) { + _format((N4EnumDeclaration) clazz, document); + return; + } else if (clazz instanceof TemplateSegment) { + _format((TemplateSegment) clazz, document); + return; + } else if (clazz instanceof IntersectionTypeExpression) { + _format((IntersectionTypeExpression) clazz, document); + return; + } else if (clazz instanceof ParameterizedTypeRef) { + _format((ParameterizedTypeRef) clazz, document); + return; + } else if (clazz instanceof ThisTypeRef) { + _format((ThisTypeRef) clazz, document); + return; + } else if (clazz instanceof UnionTypeExpression) { + _format((UnionTypeExpression) clazz, document); + return; + } else if (clazz instanceof TStructMember) { + _format((TStructMember) clazz, document); + return; + } else if (clazz instanceof ArrayLiteral) { + _format((ArrayLiteral) clazz, document); + return; + } else if (clazz instanceof ForStatement) { + _format((ForStatement) clazz, document); + return; + } else if (clazz instanceof FunctionExpression) { + _format((FunctionExpression) clazz, document); + return; + } else if (clazz instanceof IndexedAccessExpression) { + _format((IndexedAccessExpression) clazz, document); + return; + } else if (clazz instanceof N4FieldDeclaration) { + _format((N4FieldDeclaration) clazz, document); + return; + } else if (clazz instanceof ObjectLiteral) { + _format((ObjectLiteral) clazz, document); + return; + } else if (clazz instanceof ParameterizedCallExpression) { + _format((ParameterizedCallExpression) clazz, document); + return; + } else if (clazz instanceof ParameterizedPropertyAccessExpression) { + _format((ParameterizedPropertyAccessExpression) clazz, document); + return; + } else if (clazz instanceof ParenExpression) { + _format((ParenExpression) clazz, document); + return; + } else if (clazz instanceof TaggedTemplateString) { + _format((TaggedTemplateString) clazz, document); + return; + } else if (clazz instanceof TemplateLiteral) { + _format((TemplateLiteral) clazz, document); + return; + } else if (clazz instanceof VariableDeclaration) { + _format((VariableDeclaration) clazz, document); + return; + } else if (clazz instanceof VariableStatement) { + _format((VariableStatement) clazz, document); + return; + } else if (clazz instanceof AdditiveExpression) { + _format((AdditiveExpression) clazz, document); + return; + } else if (clazz instanceof AssignmentExpression) { + _format((AssignmentExpression) clazz, document); + return; + } else if (clazz instanceof AwaitExpression) { + _format((AwaitExpression) clazz, document); + return; + } else if (clazz instanceof BinaryBitwiseExpression) { + _format((BinaryBitwiseExpression) clazz, document); + return; + } else if (clazz instanceof BinaryLogicalExpression) { + _format((BinaryLogicalExpression) clazz, document); + return; + } else if (clazz instanceof Block) { + _format((Block) clazz, document); + return; + } else if (clazz instanceof CastExpression) { + _format((CastExpression) clazz, document); + return; + } else if (clazz instanceof CommaExpression) { + _format((CommaExpression) clazz, document); + return; + } else if (clazz instanceof ConditionalExpression) { + _format((ConditionalExpression) clazz, document); + return; + } else if (clazz instanceof EqualityExpression) { + _format((EqualityExpression) clazz, document); + return; + } else if (clazz instanceof ExportDeclaration) { + _format((ExportDeclaration) clazz, document); + return; + } else if (clazz instanceof ExpressionStatement) { + _format((ExpressionStatement) clazz, document); + return; + } else if (clazz instanceof IfStatement) { + _format((IfStatement) clazz, document); + return; + } else if (clazz instanceof ImportDeclaration) { + _format((ImportDeclaration) clazz, document); + return; + } else if (clazz instanceof MultiplicativeExpression) { + _format((MultiplicativeExpression) clazz, document); + return; + } else if (clazz instanceof NamespaceImportSpecifier) { + _format((NamespaceImportSpecifier) clazz, document); + return; + } else if (clazz instanceof NewExpression) { + _format((NewExpression) clazz, document); + return; + } else if (clazz instanceof PostfixExpression) { + _format((PostfixExpression) clazz, document); + return; + } else if (clazz instanceof PromisifyExpression) { + _format((PromisifyExpression) clazz, document); + return; + } else if (clazz instanceof RelationalExpression) { + _format((RelationalExpression) clazz, document); + return; + } else if (clazz instanceof ReturnStatement) { + _format((ReturnStatement) clazz, document); + return; + } else if (clazz instanceof ShiftExpression) { + _format((ShiftExpression) clazz, document); + return; + } else if (clazz instanceof SwitchStatement) { + _format((SwitchStatement) clazz, document); + return; + } else if (clazz instanceof ThrowStatement) { + _format((ThrowStatement) clazz, document); + return; + } else if (clazz instanceof UnaryExpression) { + _format((UnaryExpression) clazz, document); + return; + } else if (clazz instanceof VariableBinding) { + _format((VariableBinding) clazz, document); + return; + } else if (clazz instanceof YieldExpression) { + _format((YieldExpression) clazz, document); + return; + } else if (clazz instanceof XtextResource) { + _format((XtextResource) clazz, document); + return; + } else if (clazz instanceof AbstractCaseClause) { + _format((AbstractCaseClause) clazz, document); + return; + } else if (clazz instanceof BindingPattern) { + _format((BindingPattern) clazz, document); + return; + } else if (clazz instanceof CatchBlock) { + _format((CatchBlock) clazz, document); + return; + } else if (clazz instanceof Expression) { + _format((Expression) clazz, document); + return; + } else if (clazz instanceof FinallyBlock) { + _format((FinallyBlock) clazz, document); + return; + } else if (clazz instanceof FunctionOrFieldAccessor) { + _format((FunctionOrFieldAccessor) clazz, document); + return; + } else if (clazz instanceof N4TypeVariable) { + _format((N4TypeVariable) clazz, document); + return; + } else if (clazz instanceof NamedImportSpecifier) { + _format((NamedImportSpecifier) clazz, document); + return; + } else if (clazz instanceof Script) { + _format((Script) clazz, document); + return; + } else if (clazz instanceof EObject) { + _format((EObject) clazz, document); + return; + } else if (clazz == null) { + _format((Void) null, document); + return; + } else { + _format(clazz, document); + return; + } + } + + private void configureAnnotations(final Object semEObject, final IFormattableDocument document) { + if (semEObject instanceof AnnotablePropertyAssignment) { + _configureAnnotations((AnnotablePropertyAssignment) semEObject, document); + return; + } else if (semEObject instanceof AnnotableExpression) { + _configureAnnotations((AnnotableExpression) semEObject, document); + return; + } else if (semEObject instanceof AnnotableN4MemberDeclaration) { + _configureAnnotations((AnnotableN4MemberDeclaration) semEObject, document); + return; + } else if (semEObject instanceof AnnotableScriptElement) { + _configureAnnotations((AnnotableScriptElement) semEObject, document); + return; + } else if (semEObject instanceof AbstractAnnotationList) { + _configureAnnotations((AbstractAnnotationList) semEObject, document); + return; + } else if (semEObject == null) { + _configureAnnotations((Void) null, document); + return; + } else { + _configureAnnotations(semEObject, document); + return; + } + } + + private Integer maxConsecutiveNewLines() { + // let's stick to 2 + return getPreference(FORMAT_MAX_CONSECUTIVE_NEWLINES); + } + + void _format(Script script, IFormattableDocument document) { + N4JSGenericFormatter generic = new N4JSGenericFormatter(grammarAccess, textRegionExtensions); + if (getPreference(FORMAT_PARENTHESIS)) { + // script.formatParenthesisBracketsAndBraces(document) + } + + // TODO the following line requires more conflict handling with semicolons: + // script.interior[noIndentation;]; + + generic.formatSemicolons(script, document); + generic.formatColon(script, document); + + formatScriptAnnotations(script, document); + + for (ScriptElement element : script.getScriptElements()) { + // element.append[setNewLines(1, 1, maxConsecutiveNewLines);hrf.noSpace(); autowrap].prepend, + // hrf->hrf.noSpace(); + document.append(element, hrf -> { + hrf.setNewLines(1, 1, maxConsecutiveNewLines()); + hrf.autowrap(); + }); + + document.format(element); + } + + // format last import, overrides default newLines: + document.append(last(filter(script.getScriptElements(), ImportDeclaration.class)), hfr -> { + hfr.setNewLines(2, 2, 3); + hfr.highPriority(); + }); + } + + /** put modifiers into a single line separated by one space, last modifier has one space to following element. */ + void configureModifiers(EObject semObject, IFormattableDocument document) { + for (ISemanticRegion sr : textRegionExtensions.regionFor(semObject) + .ruleCallsTo(grammarAccess.getN4ModifierRule())) { + + document.append(sr, hrf -> hrf.oneSpace()); + } + } + + void configureTypingStrategy(EObject semObject, IFormattableDocument document) { + for (ISemanticRegion sr : textRegionExtensions.regionFor(semObject) + .ruleCallsTo(grammarAccess.getTypingStrategyDefSiteOperatorRule(), + grammarAccess.getTypingStrategyUseSiteOperatorRule())) { + + document.append(sr, hrf -> hrf.noSpace()); + } + } + + void formatTypeVariables(GenericDeclaration semObject, IFormattableDocument document) { + if (semObject.getTypeVars().isEmpty()) { + return; + } + // to "<": + ISemanticRegion sr = document.prepend(textRegionExtensions.regionFor(semObject).keyword("<"), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + }); + document.append(sr, hrf -> hrf.noSpace()); + + document.prepend(textRegionExtensions.regionFor(semObject).keyword(">"), hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }); + + for (N4TypeVariable typeVar : semObject.getTypeVars()) { + N4TypeVariable sr2 = document.append(typeVar, hrf -> hrf.noSpace()); + document.append(textRegionExtensions.immediatelyFollowing(sr2).keyword(","), hrf -> hrf.oneSpace()); + format(typeVar, document); + } + } + + void _format(N4ClassDeclaration clazz, IFormattableDocument document) { + + configureAnnotations(clazz, document); + insertSpaceInFrontOfCurlyBlockOpener(clazz, document); + indentExcludingAnnotations(clazz, document); + + configureTypingStrategy(clazz, document); + configureModifiers(clazz, document); + + formatTypeVariables(clazz, document); + + // val semRegModifier = textRegionExtensions.regionFor(clazz).feature( + // N4JSPackage.Literals.MODIFIABLE_ELEMENT__DECLARED_MODIFIERS); + // if( semRegModifier != null ) { // only if exists. + // val beginModifierHR = semRegModifier.previousHiddenRegion; + // val endModifierHR = semRegModifier.nextHiddenRegion; + // // go over all semantic regions in the modifier location. + // var currHR = beginModifierHR.nextHiddenRegion; + // while( currHR != endModifierHR ){ + // currHR.set, hrf->hrf.oneSpace(); + // currHR = currHR.nextHiddenRegion; + // } + // endModifierHR.set, hrf->hrf.oneSpace(); + // } // end modifier formatting TODO extract into method. + + // TODO revise the following pattern of call-back implementations. + // Define lambda for callback & normal use: + Function twolinesBeforeFirstMember = (prio) -> { + return document.prepend(clazz.getOwnedMembersRaw().get(0), hrf -> { + hrf.setNewLines(2); + hrf.setPriority(prio); + }); + }; + + // Defines CallBack for autoWrap: + IAutowrapFormatter callBackOnAutoWrap = new IAutowrapFormatter() { // callback for auto-wrapping with implements + boolean didReconfigure = false; // track to only execute once. + + @SuppressWarnings("hiding") + @Override + public void format(ITextSegment region, IHiddenRegionFormatting wrapped, IFormattableDocument document) { + if (!didReconfigure) { + twolinesBeforeFirstMember.apply(IHiddenRegionFormatter.HIGH_PRIORITY); // reformat with higher + // priority + didReconfigure = true; // keep state. + } + } + }; + + // 2nd version of implementing the callback: + StateTrack state2 = new StateTrack(); + IAutowrapFormatter callBackOnAutoWrap2 = (region, hrFormatting, document2) -> { + if (state2.shouldDoThenDone()) + twolinesBeforeFirstMember.apply(IHiddenRegionFormatter.HIGH_PRIORITY); + }; + + suppressUnusedWarnings(callBackOnAutoWrap2); + + // Allow for lineBreaks in front of keywords: + document.append(document.prepend(textRegionExtensions.regionFor(clazz).keyword("extends"), hrf -> { + hrf.setNewLines(0, 0, 1); // allow line break in front. + hrf.autowrap(); + }), hrf -> { + hrf.oneSpace(); + hrf.autowrap(); + }); + + document.append(document.prepend(textRegionExtensions.regionFor(clazz).keyword("implements"), hrf -> { + hrf.setNewLines(0, 0, 1); + hrf.autowrap(); + hrf.setPriority(IHiddenRegionFormatter.LOW_PRIORITY); + hrf.setOnAutowrap(callBackOnAutoWrap); + }), hrf -> { + hrf.oneSpace(); + hrf.autowrap(); + }); + + for (TypeReferenceNode trn : tail(clazz.getImplementedInterfaceRefs())) { + document.prepend(trn, hrf -> { + hrf.autowrap(); + hrf.setPriority(IHiddenRegionFormatter.LOW_PRIORITY); + hrf.setOnAutowrap(callBackOnAutoWrap); + }); + } + + // special case if the header of the class spans multiple lines, then insert extra line break. + + ISemanticRegion kwClass = textRegionExtensions.regionFor(clazz).keyword("class"); + ISemanticRegion kwBrace = textRegionExtensions.regionFor(clazz).keyword("{"); // autowrap-listener ? + if (!kwClass.getLineRegions().get(0).contains(kwBrace)) { + twolinesBeforeFirstMember.apply(IHiddenRegionFormatter.NORMAL_PRIORITY); + } else { + document.prepend(clazz.getOwnedMembersRaw().get(0), hrf -> { + hrf.setNewLines(1, 1, maxConsecutiveNewLines()); + hrf.autowrap(); + }); + } + + document.append(kwClass, hrf -> hrf.oneSpace()); + + for (N4MemberDeclaration member : clazz.getOwnedMembersRaw()) { + document.append(member, hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines())); + document.format(member); + } + + // Collapse empty block: + if (clazz.getOwnedMembersRaw().isEmpty()) { + // Empty body: + document.append(kwBrace, hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }); + } + } + + void _format(N4InterfaceDeclaration interf, IFormattableDocument document) { + configureAnnotations(interf, document); + configureModifiers(interf, document); + insertSpaceInFrontOfCurlyBlockOpener(interf, document); + indentExcludingAnnotations(interf, document);// .interiorBUGFIX(, hrf->hrf.indent(),document); + // //interf.interior, + // hrf->hrf.indent(); + + document.prepend(interf.getOwnedMembersRaw().get(0), hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines())); + for (N4MemberDeclaration member : interf.getOwnedMembersRaw()) { + document.append(member, hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines())); + document.format(member); + } + } + + // void _format(N4MemberDeclaration member, IFormattableDocument document) { + // textRegionExtensions.regionFor(member).keyword("(").prepend[hrf.noSpace(); hrf.setNewLines(1);].append, + // hrf->hrf.noSpace() + // + // member.insertSpaceInfrontOfPropertyNames(document); + // for (c : member.eContents) { + // document.format(// c); + // } + // } + + void _format(N4FieldDeclaration field, IFormattableDocument document) { + configureAnnotations(field, document); + configureModifiers(field, document); + + indentExcludingAnnotations(field, document); + + configureOptionality(field, document); + document.append(document.prepend(textRegionExtensions.regionFor(field).keyword("="), hrf -> hrf.oneSpace()), + hrf -> hrf.oneSpace()); + document.format(field.getExpression()); + document.format(field.getDeclaredTypeRefInAST()); + } + + // format(N4MethodDeclaration method, IFormattableDocument document) { + // configureAnnotations(method, document); + // method.insertSpaceInfrontOfPropertyNames(document); + // + // textRegionExtensions.regionFor(method).keyword("(").prepend[hrf.noSpace(); hrf.setNewLines(1);] + // + // method.textRegionExtensions.regionFor(body).keyword("{").prepend[hrf.oneSpace(); newLines = 0] + // for (child : method.eContents) { + // document.format(// child); + // } + // } + // + void _format(FunctionExpression funE, IFormattableDocument document) { + configureAnnotations(funE, document); + configureModifiers(funE, document); + + if (funE.isArrowFunction()) { + throw new IllegalStateException("Arrow functions should be formated differently."); + } + + configureFormalParameters(funE.getFpars(), document, + (ITextSegment $0, IHiddenRegionFormatting $1, IFormattableDocument $2) -> { + /* n.t.d. */}); + + Pair parenPair = textRegionExtensions.regionFor(funE).keywordPairs("(", ")") + .get(0); + + document.append(parenPair.getKey(), hrf -> hrf.noSpace()); + document.prepend(parenPair.getValue(), hrf -> hrf.noSpace()); + document.format(funE.getBody()); + } + + void _format(FunctionOrFieldAccessor fDecl, IFormattableDocument document) { + configureAnnotations(fDecl, document); + configureModifiers(fDecl, document); + + // State-keeper to avoid clashing reconfigurations if multiple auto-wraps get triggered. + final StateTrack state = new StateTrack(); // use state to only trigger one change, even if called multiple + // times. + + // Callback to introduce an additional line in body-block. + IAutowrapFormatter cbInsertEmptyLineInBody = ( + ITextSegment ts, IHiddenRegionFormatting hrf, IFormattableDocument fd) -> { + + if (state.shouldDoThenDone() && fDecl.getBody() != null && fDecl.getBody().getStatements() != null + && !fDecl.getBody().getStatements().isEmpty()) { + document.prepend(fDecl.getBody().getStatements().get(0), hrf2 -> { + hrf2.setNewLines(2, 2, maxConsecutiveNewLines()); + hrf2.highPriority(); + }); + } + }; + + // Formal parameters + if (fDecl instanceof FunctionDefinition) { + configureFormalParameters(((FunctionDefinition) fDecl).getFpars(), document, cbInsertEmptyLineInBody); + } else if (fDecl instanceof N4SetterDeclaration) { + /* no autowrap for setters: cbInsertEmptyLineInBody */ + document.append(document.prepend(((N4SetterDeclaration) fDecl).getFpar(), hrf -> hrf.noSpace()), + hrf -> hrf.noSpace()); + } + + // Type Variables + if (fDecl instanceof FunctionDeclaration) { + formatTypeVariables((FunctionDeclaration) fDecl, document); + } + + // special case for accessors: get / set keywords + if (fDecl instanceof FieldAccessor) { + configureGetSetKeyword((FieldAccessor) fDecl, document); + configureOptionality((FieldAccessor) fDecl, document); + } + + Pair parenPair = textRegionExtensions.regionFor(fDecl).keywordPairs("(", + ")").get(0); + + document.append(document.prepend(parenPair.getKey(), hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }), hrf -> hrf.noSpace()); + + document.interior(parenPair, hrf -> hrf.indent()); + + if (isMultiLine(parenPair) && !(fDecl instanceof FieldAccessor)) { + // it is already a multiline, insert the newLine immediately. + // cbInsertEmptyLineInBody.apply(null,null,null); // TODO re-think, if all will be collapsed this assumption + // does not hold an + } else { + // single line parameter block + } + document.prepend(parenPair.getValue(), hrf -> hrf.noSpace()); + + for (EObject child : fDecl.eContents()) { + document.format(child); + } + } + + /** to be used by FunctionDefintiions and SetterDeclarations */ + void configureFormalParameters(EList list, IFormattableDocument document, IAutowrapFormatter x) { + if (list == null || list.isEmpty()) { + return; + } + for (int idx = 0; idx < list.size(); idx++) { + FormalParameter it = list.get(idx); + if (idx != 0) { + document.prepend(it, hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0, 0, 1); + hrf.setOnAutowrap(x); + }); + } + document.append(it, hrf -> hrf.noSpace()); + configureAnnotationsInLine(it, document); // TODO maybe we need some in-line-annotation config here. + // textRegionExtensions.regionFor(it).ruleCallTo( bindingIdentifierAsFormalParameterRule ) // + // feature(N4JSPackage.Literals.FORMAL_PARAMETER__NAME) + // .prepend[hrf.oneSpace();hrf.setNewLines(1);].append[] + format(it.getDeclaredTypeRefInAST(), document); + if (it.isVariadic()) { + document.append(document.prepend(textRegionExtensions.regionFor(it).keyword("..."), + hrf -> hrf.setNewLines(0)/* hrf.oneSpace(); */), hrf -> { + hrf.setNewLines(0); + hrf.noSpace(); + }); + } + if (it.isHasInitializerAssignment()) { + document.append(document.prepend(textRegionExtensions.regionFor(it).keyword("="), hrf -> { + hrf.setNewLines(0); + hrf.noSpace(); + }), hrf -> { + hrf.setNewLines(0); + hrf.noSpace(); + }); + if (it.getInitializer() != null) { + format(it.getInitializer(), document); + } + } + } + } + + /** Check if key and value are in different lines. Defined for non-overlapping Regions, e.g. Keyword-pairs. */ + static boolean isMultiLine(Pair pair) { + return !last(pair.getKey().getLineRegions()).contains(pair.getValue()); + } + + // format(FunctionOrFieldAccessor fofAccessor, IFormattableDocument document) { + // val begin = fofAccessor.body.semanticRegions.head + // val end = fofAccessor.body.semanticRegions.last + // if (begin?.lineRegions?.head?.contains(end?.endOffset)) { + // // same line + // } else { + // // body spans multiple lines + // begin.append[newLine;]; + // end.prepend[newLine;]; + // // fofAccessor.body.interior, hrf->hrf.indent(); // already by parenthesis? + // } + // + // document.format(// fofAccessor.body?); + // + // } + + void _format(N4EnumDeclaration enumDecl, IFormattableDocument document) { + configureAnnotations(enumDecl, document); + configureModifiers(enumDecl, document); + insertSpaceInFrontOfCurlyBlockOpener(enumDecl, document); + indentExcludingAnnotations(enumDecl, document);// .interiorBUGFIX(, hrf->hrf.indent(),document); + // //enumDecl.interior, hrf->hrf.indent(); + configureCommas(enumDecl, document); + + Pair braces = textRegionExtensions.regionFor(enumDecl).keywordPairs("{", "}") + .get(0); + + boolean multiLine = textRegionExtensions.isMultiline(enumDecl); + + for (N4EnumLiteral it : enumDecl.getLiterals()) { + format(it, document); + if (multiLine) { + if (textRegionExtensions.regionForEObject(it).getPreviousHiddenRegion().containsComment()) { + // comment above + document.prepend(it, hrf -> hrf.setNewLines(2)); + } else { // no comment above + document.prepend(it, hrf -> hrf.newLine()); + } + } + } + if (multiLine) { + document.prepend(braces.getValue(), hrf -> hrf.newLine()); + } + } + + void _format(ParameterizedPropertyAccessExpression exp, IFormattableDocument document) { + ISemanticRegion dotKW = textRegionExtensions.regionFor(exp).keyword("."); + document.append(document.prepend(dotKW, hrf -> { + hrf.noSpace(); + hrf.autowrap(); + hrf.setNewLines(0, 0, 1); + }), hrf -> hrf.noSpace()); + if (exp.eContainer() instanceof ExpressionStatement) { + // top-level PPA, indent one level. + interiorBUGFIX(exp, hrf -> hrf.indent(), document); // exp.interior, hrf->hrf.indent(); + } + document.format(exp.getTarget()); + } + + void _format(ParameterizedCallExpression exp, IFormattableDocument document) { + // FIXME process typeArgs !!! + ISemanticRegion dotKW = textRegionExtensions.regionFor(exp).keyword("."); + document.append(document.prepend(dotKW, hrf -> { + hrf.noSpace(); + hrf.autowrap(); + }), hrf -> hrf.noSpace()); + document.append(document.prepend(textRegionExtensions.regionFor(exp).keyword("("), hrf -> hrf.noSpace()), + hrf -> hrf.noSpace()); + document.prepend(textRegionExtensions.regionFor(exp).keyword(")"), hrf -> hrf.noSpace()); + configureCommas(exp, document); + + for (Argument arg : tail(exp.getArguments())) { + document.prepend(arg, hrf -> { + hrf.oneSpace(); + hrf.autowrap(); + }); + } + + for (Argument arg : exp.getArguments()) { + format(arg, document); + } + + if (exp.eContainer() instanceof ExpressionStatement) { + // top-level PPA, indent one level. + interiorBUGFIX(exp, hrf -> hrf.indent(), document); // exp.interior, hrf->hrf.indent(); + } + document.format(exp.getTarget()); + } + + void _format(ImportDeclaration decl, IFormattableDocument document) { + // read configuration: + boolean extraSpace = getPreference(FORMAT_SURROUND_IMPORT_LIST_WITH_SPACE); + + document.append(document.prepend(textRegionExtensions.regionFor(decl).keyword("{"), hrf -> hrf.noSpace()), + hrf -> { + if (extraSpace) + hrf.oneSpace(); + else + hrf.noSpace(); + }); + document.append(document.prepend(textRegionExtensions.regionFor(decl).keyword("}"), hrf -> { + if (extraSpace) + hrf.oneSpace(); + else + hrf.noSpace(); + }), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + }); + document.surround(textRegionExtensions.regionFor(decl).keyword("from"), hrf -> hrf.oneSpace()); + configureCommas(decl, document); + for (EObject eobj : decl.eContents()) { + format(eobj, document); + } + } + + void _format(NamedImportSpecifier namedImp, IFormattableDocument document) { + document.append(document.prepend(textRegionExtensions.regionFor(namedImp).keyword("as"), hrf -> hrf.oneSpace()), + hrf -> hrf.oneSpace()); + // "+"-KW after alias-name + document.append(document.prepend( + textRegionExtensions.regionFor(namedImp) + .feature(N4JSPackage.Literals.IMPORT_SPECIFIER__DECLARED_DYNAMIC), + hrf -> hrf.noSpace()), + hrf -> hrf.oneSpace()); + } + + void _format(NamespaceImportSpecifier nsImp, IFormattableDocument document) { + document.append(textRegionExtensions.regionFor(nsImp).keyword("*"), hrf -> hrf.oneSpace()); + document.append(textRegionExtensions.regionFor(nsImp).keyword("as"), hrf -> hrf.oneSpace()); + // "+"-KW after alias-name + document.append(document.prepend(textRegionExtensions.regionFor(nsImp) + .feature(N4JSPackage.Literals.IMPORT_SPECIFIER__DECLARED_DYNAMIC), + hrf -> hrf.noSpace()), + hrf -> hrf.oneSpace()); + } + + void _format(ExportDeclaration export, IFormattableDocument document) { + document.append(textRegionExtensions.regionFor(export).keyword("export"), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(1); + // Apply prioritization to catch cases of 'trapped' annotations e.g. "export @Final public class" which + // could also be reordered to "@Final export public class.." + hrf.setPriority(PRIO_13); // Priority higher then highPriority used in AnnotationList. + }); + + for (EObject eo : export.eContents()) { + format(eo, document); + } + + // Fix Trapped annotations: + ExportableElement exported = export.getExportedElement(); + if (exported instanceof AnnotableScriptElement) { + AnnotationList annoList = ((AnnotableScriptElement) exported).getAnnotationList(); + if (annoList != null && !annoList.getAnnotations().isEmpty()) { + document.append(last(annoList.getAnnotations()), hrf -> { + hrf.setNewLines(0); + hrf.oneSpace(); + hrf.setPriority(PRIO_13); + }); + } + } + } + + void _format(IfStatement stmt, IFormattableDocument document) { + Pair parenPair = textRegionExtensions.regionFor(stmt).keywordPairs("(", ")") + .get(0); + document.interior(parenPair, hrf -> { + hrf.noSpace(); + hrf.indent(); + }); + document.prepend(parenPair.getKey(), hrf -> hrf.oneSpace()); + document.append(parenPair.getValue(), hrf -> hrf.oneSpace()); + + document.append(document.prepend(textRegionExtensions.regionFor(stmt).keyword("else"), hrf -> { + hrf.autowrap(); + hrf.oneSpace(); + }), hrf -> hrf.oneSpace()); + + document.prepend(stmt.getElseStmt(), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + }); + + document.format(stmt.getExpression()); + document.format(stmt.getIfStmt()); + document.format(stmt.getElseStmt()); + + } + + void _format(SwitchStatement swStmt, IFormattableDocument document) { + insertSpaceInFrontOfCurlyBlockOpener(swStmt, document); + interiorBUGFIX(swStmt, hrf -> hrf.indent(), document); // swStmt.interior, hrf->hrf.indent(); + document.format(swStmt.getExpression()); + document.prepend(swStmt.getCases().get(0), hrf -> hrf.newLine()); + + for (EObject eo : swStmt.getCases()) { + format(eo, document); + } + } + + /** Formats DefaultCaseClause + CaseClause */ + void _format(AbstractCaseClause caseClause, IFormattableDocument document) { + interiorBUGFIX(caseClause, hrf -> hrf.indent(), document); // caseClause.interior, hrf->hrf.indent(); + + EList stmts = caseClause.getStatements(); + + if (stmts.size() == 1) { + if (stmts.get(0) instanceof Block) { + document.prepend(stmts.get(0), hrf -> hrf.setNewLines(0, 0, 0)); + } else { + document.prepend(stmts.get(0), hrf -> hrf.setNewLines(0, 1, 1)); + } + } else { + document.prepend(stmts.get(0), hrf -> hrf.setNewLines(1, 1, 1)); + } + + // textRegionExtensions.regionFor(caseClause).keyword(":").prepend, hrf->hrf.oneSpace(); // In case one space + // before the colon is desired + for (EObject eo : stmts) { + format(eo, document); + } + for (EObject eo : stmts) { + document.append(eo, hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines())); + } + + document.append(caseClause, hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines())); + } + + void _format(CastExpression expr, IFormattableDocument document) { + document.append(document.prepend(textRegionExtensions.regionFor(expr).keyword("as"), hrf -> { + hrf.setNewLines(0); + hrf.oneSpace(); + }), hrf -> { + hrf.setNewLines(0); + hrf.oneSpace(); + }); + document.format(expr.getExpression()); + document.format(expr.getTargetTypeRefNode()); + } + + void _format(Block block, IFormattableDocument document) { + if (debug) { + LOGGER.debug("Formatting block " + containmentStructure(block)); + } + + // Beware there are blocks in the grammar, that are not surrounded by curly braces. (e.g. FunctionExpression) + + // Block not nested in other blocks usually are bodies. We want them separated by a space: + if (!(block.eContainer() instanceof Block || block.eContainer() instanceof Script)) { + // TODO maybe invert the control here, since the block is formatting the outside. + document.prepend(textRegionExtensions.regionFor(block).keyword("{"), hrf -> hrf.oneSpace()); + } + + interiorBUGFIX(block, hrf -> hrf.indent(), document); // block.interior, hrf->hrf.indent(); + + document.prepend(block.getStatements().get(0), hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines())); + + for (Statement s : block.getStatements()) { + document.append(s, hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines())); + } + + for (Statement s : block.getStatements()) { + document.format(s); + } + + // Format empty curly blocks, necessary for comments inside: + Pair braces = textRegionExtensions.regionFor(block).keywordPairs("{", "}") + .get(0); + if (braces != null && braces.getKey().getNextSemanticRegion() == braces.getValue()) { + // empty block: + if (braces.getKey().getNextHiddenRegion().containsComment()) { + document.append(braces.getKey(), hrf -> hrf.setNewLines(1, 1, maxConsecutiveNewLines())); + } else { + document.append(braces.getKey(), hrf -> { + hrf.setNewLines(1); + hrf.noSpace(); + }); + } + } + } + + void _format(ReturnStatement ret, IFormattableDocument document) { + interiorBUGFIX(ret, hrf -> hrf.indent(), document); // ret.interior[indent;] + document.prepend(ret.getExpression(), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + }); + document.format(ret.getExpression()); + } + + void _format(AdditiveExpression add, IFormattableDocument document) { + document.append(document.surround( + textRegionExtensions.regionFor(add).feature(N4JSPackage.Literals.ADDITIVE_EXPRESSION__OP), + hrf -> hrf.oneSpace()), hrf -> hrf.autowrap()); + document.format(add.getLhs()); + document.format(add.getRhs()); + } + + void _format(MultiplicativeExpression mul, IFormattableDocument document) { + document.append(document.surround( + textRegionExtensions.regionFor(mul).feature(N4JSPackage.Literals.MULTIPLICATIVE_EXPRESSION__OP), + hrf -> hrf.oneSpace()), hrf -> hrf.autowrap()); + document.format(mul.getLhs()); + document.format(mul.getRhs()); + } + + void _format(BinaryBitwiseExpression binbit, IFormattableDocument document) { + document.surround(textRegionExtensions.regionFor(binbit) + .feature(N4JSPackage.Literals.BINARY_BITWISE_EXPRESSION__OP), hrf -> hrf.oneSpace()); + document.format(binbit.getLhs()); + document.format(binbit.getRhs()); + } + + void _format(BinaryLogicalExpression binLog, IFormattableDocument document) { + ISemanticRegion opReg = textRegionExtensions.regionFor(binLog) + .feature(N4JSPackage.Literals.BINARY_LOGICAL_EXPRESSION__OP); + document.surround(opReg, hrf -> hrf.oneSpace()); + document.format(binLog.getLhs()); + document.format(binLog.getRhs()); + // auto-wrap: + boolean autoWrapInFront = getPreference(FORMAT_AUTO_WRAP_IN_FRONT_OF_LOGICAL_OPERATOR); + if (autoWrapInFront) { + document.prepend(opReg, hrf -> { + hrf.autowrap(); + hrf.lowPriority(); + hrf.setNewLines(0, 0, 1); + }); + } else { + document.append(opReg, hrf -> { + hrf.autowrap(); + hrf.lowPriority(); + hrf.setNewLines(0, 0, 1); + }); + } + } + + void _format(EqualityExpression eqExpr, IFormattableDocument document) { + document.append(document.surround( + textRegionExtensions.regionFor(eqExpr).feature(N4JSPackage.Literals.EQUALITY_EXPRESSION__OP), + hrf -> hrf.oneSpace()), hrf -> hrf.autowrap()); + document.format(eqExpr.getLhs()); + document.format(eqExpr.getRhs()); + } + + void _format(RelationalExpression relExpr, IFormattableDocument document) { + document.append(document.surround( + textRegionExtensions.regionFor(relExpr).feature(N4JSPackage.Literals.RELATIONAL_EXPRESSION__OP), + hrf -> hrf.oneSpace()), hrf -> hrf.autowrap()); + document.format(relExpr.getLhs()); + document.format(relExpr.getRhs()); + } + + void _format(ShiftExpression shiftExpr, IFormattableDocument document) { + document.append(document.surround( + textRegionExtensions.regionFor(shiftExpr).feature(N4JSPackage.Literals.SHIFT_EXPRESSION__OP), + hrf -> hrf.oneSpace()), hrf -> hrf.autowrap()); + document.format(shiftExpr.getLhs()); + document.format(shiftExpr.getRhs()); + } + + void _format(CommaExpression comma, IFormattableDocument document) { + configureCommas(comma, document); + for (EObject eo : comma.eContents()) { + document.format(eo); + } + } + + void _format(ConditionalExpression cond, IFormattableDocument document) { + document.append(document.surround(textRegionExtensions.regionFor(cond).keyword("?"), hrf -> hrf.oneSpace()), + hrf -> { + hrf.autowrap(); + hrf.lowPriority(); + hrf.setNewLines(0, 0, 1); + }); + document.append(document.surround(textRegionExtensions.regionFor(cond).keyword(":"), hrf -> hrf.oneSpace()), + hrf -> { + hrf.autowrap(); + hrf.lowPriority(); + hrf.setNewLines(0, 0, 1); + }); + document.format(cond.getExpression()); + document.format(cond.getTrueExpression()); + document.format(cond.getFalseExpression()); + } + + void _format(AwaitExpression await, IFormattableDocument document) { + document.append(document.prepend(textRegionExtensions.regionFor(await).keyword("await"), hrf -> hrf.oneSpace()), + hrf -> { + hrf.oneSpace(); + hrf.setNewLines(1); + }); + document.format(await.getExpression()); + } + + void _format(PromisifyExpression promify, IFormattableDocument document) { + noSpaceAfterAT(promify, document); + document.append(textRegionExtensions.regionFor(promify).keyword("Promisify"), hrf -> hrf.oneSpace()); + document.format(promify.getExpression()); + } + + void _format(IndexedAccessExpression idxAcc, IFormattableDocument document) { + IEObjectRegion indexRegion = textRegionExtensions.regionForEObject(idxAcc.getIndex()); + document.append(document.prepend(indexRegion.getPreviousSemanticRegion(), hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + }), hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + }); + document.prepend(indexRegion.getNextSemanticRegion(), hrf -> hrf.noSpace()); + + document.format(idxAcc.getIndex()); + document.format(idxAcc.getTarget()); + } + + void _format(NewExpression newExp, IFormattableDocument document) { + document.append(document.prepend(textRegionExtensions.regionFor(newExp).keyword("new"), hrf -> hrf.oneSpace()), + hrf -> { + hrf.oneSpace(); + hrf.setNewLines(1); + }); + document.format(newExp.getCallee()); + // Watch out, commas are used in Type-args and in argument list ! If necessary distinguish by offset. + ISemanticRegion commas = textRegionExtensions.regionFor(newExp).keyword(","); + document.append(document.prepend(commas, hrf -> hrf.noSpace()), hrf -> hrf.oneSpace()); + + // TODO maybe factor out TypeArgs formatting. + Pair typeArgsAngle = textRegionExtensions.regionFor(newExp) + .keywordPairs("<", ">").get(0); + + if (typeArgsAngle != null) { + document.append(document.prepend(typeArgsAngle.getKey(), hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + }), hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + }); + document.prepend(typeArgsAngle.getValue(), hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + }); + } + for (TypeReferenceNode trn : newExp.getTypeArgs()) { + document.format(trn); + } + + if (newExp.isWithArgs()) { + Pair argParen = textRegionExtensions.regionFor(newExp) + .keywordPairs("(", ")").get(0); + document.append(document.prepend(argParen.getKey(), hrf -> { + hrf.setNewLines(1); + hrf.noSpace(); + }), hrf -> hrf.noSpace()); + document.prepend(argParen.getValue(), hrf -> hrf.noSpace()); + for (Argument arg : newExp.getArguments()) { + document.format(arg); + } + } + } + + void _format(PostfixExpression postFix, IFormattableDocument document) { + // no line break allowed between Expression and operator ! + document.append(document.prepend( + textRegionExtensions.regionFor(postFix).feature(N4JSPackage.Literals.POSTFIX_EXPRESSION__OP), hrf -> { + hrf.setNewLines(1); + hrf.noSpace(); + }), hrf -> { + hrf.oneSpace(); + hrf.lowPriority(); + }); + // giving low priority for situations of closing parenthesis: "(a++)" + document.format(postFix.getExpression()); + } + + void _format(TaggedTemplateString taggedTemplate, IFormattableDocument document) { + document.append(textRegionExtensions.regionFor(taggedTemplate) + .feature(N4JSPackage.Literals.EXPRESSION_WITH_TARGET__TARGET), hrf -> { + hrf.setNewLines(1); + hrf.oneSpace(); + }); + document.format(taggedTemplate.getTarget()); + document.format(taggedTemplate.getTemplate()); + } + + void _format(UnaryExpression unaryExpr, IFormattableDocument document) { + // The operators 'void' 'delete' and 'typeof' must be separated from operand. + boolean requireSpace = (unaryExpr.getOp().ordinal() <= UnaryOperator.TYPEOF_VALUE); + document.append(textRegionExtensions.regionFor(unaryExpr).feature(N4JSPackage.Literals.UNARY_EXPRESSION__OP), + hrf -> { + if (requireSpace) + hrf.oneSpace(); + else + hrf.noSpace(); + hrf.setNewLines(1); + }); + + document.format(unaryExpr.getExpression()); + } + + void _format(YieldExpression yieldExpr, IFormattableDocument document) { + // " yield " or " yield* " + document.append(document.prepend(textRegionExtensions.regionFor(yieldExpr).keyword("yield"), + hrf -> hrf.oneSpace()), + hrf -> { + if (yieldExpr.isMany()) { + hrf.noSpace(); + } else { + hrf.oneSpace(); + } + }); + + if (yieldExpr.isMany()) { + document.append(document.prepend(textRegionExtensions.regionFor(yieldExpr).keyword("*"), + hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + }), + hrf -> hrf.oneSpace()); + } + document.format(yieldExpr.getExpression()); + } + + void _format(ParenExpression parenE, IFormattableDocument document) { + document.append(head(textRegionExtensions.semanticRegions(parenE)), hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + hrf.autowrap(); + }); + document.prepend(last(textRegionExtensions.semanticRegions(parenE)), hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + hrf.autowrap(); + }); + interiorBUGFIX(parenE, hrf -> hrf.indent(), document); // parenE.interior, hrf->hrf.indent(); + document.format(parenE.getExpression()); + } + + void _format(ArrowFunction arrowF, IFormattableDocument document) { + configureCommas(arrowF, document); + document.surround(textRegionExtensions.regionFor(arrowF).ruleCallTo(grammarAccess.getArrowRule()), + hrf -> hrf.oneSpace()); + document.interior(head(textRegionExtensions.regionFor(arrowF).keywordPairs("(", ")")), hrf -> hrf.noSpace()); + // too lax: arrowF.fpars.configureFormalParameters(document,[/*NTD*/]); + + if (arrowF.isHasBracesAroundBody()) { + + // format body as block. NOTE: this block differs from other blocks, since the curly braces are defined in + // the ArrowExpression. special handling of indentation in inside the braces. + Pair bracesPair = textRegionExtensions.regionFor(arrowF).keywordPairs("{", + "}").get(0); + document.interior(bracesPair, hrf -> hrf.indent()); + + if (last(bracesPair.getKey().getLineRegions()).contains(bracesPair.getValue()) + // one line '{ do; stuff; }' + || last(bracesPair.getKey().getLineRegions()).contains(bracesPair.getKey().getNextSemanticRegion()) + // no line-break after braces e.g. '{ do; \n stuff; }' + ) { + // one line + if (arrowF.getBody() != null) { + for (int idx = 0; idx < arrowF.getBody().getStatements().size(); idx++) { + Statement it = arrowF.getBody().getStatements().get(idx); + document.format(it); + if (idx != 0) { + document.prepend(it, hrf -> { + hrf.oneSpace(); + hrf.autowrap(); + hrf.setNewLines(1); + }); + } + } + } + + // do not autowrap after "{" to keep wrap-semantic + document.append(bracesPair.getKey(), hrf -> hrf.oneSpace()); + document.prepend(bracesPair.getValue(), hrf -> hrf.oneSpace()); + } + + else { + // multi-line + if (arrowF.getBody() != null && !arrowF.getBody().getStatements().isEmpty()) { + document.prepend(arrowF.getBody().getStatements().get(0), hrf -> hrf.setNewLines(1)); + for (Statement it : arrowF.getBody().getStatements()) { + document.format(it); + document.append(it, hrf -> hrf.setNewLines(1)); + } + + } else { + // empty block, squash interior. + document.append(bracesPair.getKey(), hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + }); + document.prepend(bracesPair.getValue(), hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + }); + } + } + + } else if (arrowF.getBody() != null) { + // no braces Around the implicit return statement. + document.format(arrowF.getBody().getStatements().get(0)); + } + } + + void _format(ArrayLiteral al, IFormattableDocument document) { + Pair bracketPair = textRegionExtensions.regionFor(al).keywordPairs("[", "]") + .get(0); + document.interior(bracketPair, hrf -> hrf.indent()); + boolean sameLine = bracketPair.getKey().getLineRegions().get(0).contains(bracketPair.getValue()); + // auto wrap in front of AL-Elements, to preserve comma at end. + if (!sameLine) { + document.append(last(al.getElements()), hrf -> hrf.autowrap()); + for (int num = 0; num < al.getElements().size(); num++) { + ArrayElement it = al.getElements().get(num); + final int numF = num; + document.append(document.prepend(it, hrf -> { + hrf.autowrap(); + hrf.setNewLines(0, 0, 1); + if (numF != 0) { + hrf.oneSpace(); + } + }), hrf -> hrf.noSpace()); + } + // format last bracket if in single.line. + if (!last(bracketPair.getValue().getPreviousSemanticRegion().getLineRegions()) + .contains(bracketPair.getValue())) { + document.prepend(bracketPair.getValue(), hrf -> hrf.newLine()); + } + } else { + for (int num = 0; num < al.getElements().size(); num++) { + ArrayElement it = al.getElements().get(num); + final int numF = num; + document.prepend(it, hrf -> { + hrf.autowrap(); + if (numF != 0) { + hrf.oneSpace(); + } + }); + } + } + } + + void _format(ObjectLiteral ol, IFormattableDocument document) { + configureCommas(ol, document); + + Pair bracePair = textRegionExtensions.regionFor(ol).keywordPairs("{", + "}").get(0); + document.interior(bracePair, hrf -> hrf.indent()); + + // Decide on multiline or not. + // Rule: if opening brace is preceded by a line break, then go multiline. + boolean sameLine = bracePair.getKey().getLineRegions().get(0) + .contains(bracePair.getKey().getNextSemanticRegion().getLineRegions().get(0)); + // OLD: val sameLine = bracePair.key.lineRegions.head.contains( bracePair.value ); + + if (!sameLine) { + // format WS in front of closing brace + document.prepend(bracePair.getValue(), hrf -> hrf.newLine()); + for (PropertyAssignment it : ol.getPropertyAssignments()) { + document.prepend(it, hrf -> hrf.newLine()); + } + + if ((bracePair.getKey().getNextSemanticRegion()) == bracePair.getValue()) { + // empty multiline, trigger formatting: + document.append(bracePair.getKey(), hrf -> hrf.newLine()); + } + + } else { // in one line + document.append(bracePair.getKey(), hrf -> hrf.setNewLines(1)); + for (int num = 0; num < ol.getPropertyAssignments().size(); num++) { + PropertyAssignment it = ol.getPropertyAssignments().get(num); + + final int numF = num; + document.prepend(it, hrf -> { + hrf.setNewLines(1); + if (numF != 0) { + hrf.autowrap(); + hrf.oneSpace(); + } else { + hrf.noSpace(); + } + }); + } + // low priority to avoid conflict with dangling commas + document.prepend(bracePair.getValue(), hrf -> { + hrf.setNewLines(1); + hrf.noSpace(); + hrf.lowPriority(); + }); + } + + for (EObject eo : ol.eContents()) { + format(eo, document); + } + } + + void _format(ForStatement fst, IFormattableDocument document) { + + document.append(textRegionExtensions.regionFor(fst).keyword("for"), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(1); + hrf.autowrap(); + }); + + Pair parenPair = textRegionExtensions.regionFor(fst).keywordPairs("(", + ")").get(0); + document.append(parenPair.getKey(), hrf -> { + hrf.noSpace(); + hrf.autowrap(); + hrf.setNewLines(1); + }); + document.append(document.prepend(parenPair.getValue(), hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + }), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(1); + hrf.autowrap(); + }); + + for (ISemanticRegion it : textRegionExtensions.regionFor(fst).keywords("in", "of")) { + document.surround(it, hrf -> { + hrf.oneSpace(); + hrf.setNewLines(1); + hrf.autowrap(); + }); + } + + for (ISemanticRegion it : textRegionExtensions.regionFor(fst).keywords(";")) { + document.append(document.prepend(it, hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + }), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(1); + hrf.autowrap(); + }); + } + + for (EObject eo : fst.eContents()) { + format(eo, document); + } + } + + void _format(TemplateLiteral tl, IFormattableDocument document) { + interiorBUGFIX(tl, hrf -> hrf.indent(), document); // tl.interior[indent;]; + for (Expression it : tl.getSegments()) { + if (it instanceof TemplateSegment) { + noOp(); + } else { + document.surround(it, hrf -> { + hrf.oneSpace(); + hrf.autowrap(); + }); + } + document.format(it); + } + } + + private void noOp() { + // empty + } + + @SuppressWarnings("unused") + void _format(TemplateSegment tl, IFormattableDocument document) { + // just leave as is. + } + + void _format(N4TypeVariable tv, IFormattableDocument document) { + // "out" + if (tv.isDeclaredCovariant()) { + document.append(textRegionExtensions.regionFor(tv) + .feature(N4JSPackage.Literals.N4_TYPE_VARIABLE__DECLARED_COVARIANT), hrf -> hrf.oneSpace()); + } + // "in" + if (tv.isDeclaredContravariant()) { + document.append(textRegionExtensions.regionFor(tv) + .feature(N4JSPackage.Literals.N4_TYPE_VARIABLE__DECLARED_CONTRAVARIANT), hrf -> hrf.oneSpace()); + } + + TypeReferenceNode upperBoundNode = tv.getDeclaredUpperBoundNode(); + + if (upperBoundNode != null) { + // "extends" + document.surround(textRegionExtensions.regionFor(tv).keyword("extends"), hrf -> hrf.oneSpace()); + document.surround(textRegionExtensions.immediatelyFollowing(upperBoundNode).keyword("&"), + hrf -> hrf.oneSpace()); + format(upperBoundNode, document); + } + + } + + @SuppressWarnings("unused") + void _format(Expression exp, IFormattableDocument document) { + // Things not to format: + if (exp instanceof BooleanLiteral + || exp instanceof IdentifierRef + || exp instanceof IntLiteral + || exp instanceof NullLiteral + || exp instanceof NumberLiteral + || exp instanceof RegularExpressionLiteral + || exp instanceof StringLiteral + || exp instanceof ThisLiteral + || exp instanceof SuperLiteral + || exp instanceof JSXElement) { + return; + } + + throw new UnsupportedOperationException( + "expression " + exp.getClass().getSimpleName() + " not yet implemented."); + } + + /** simply formats all content */ + void genericFormat(Expression exp, IFormattableDocument document) { + for (EObject eo : exp.eContents()) { + format(eo, document); + } + } + + void _format(AssignmentExpression ass, IFormattableDocument document) { + document.append(ass.getLhs(), hrf -> hrf.oneSpace()); + document.prepend(ass.getRhs(), hrf -> hrf.oneSpace()); + document.format(ass.getLhs()); + document.format(ass.getRhs()); + } + + void _format(ExpressionStatement eStmt, IFormattableDocument document) { + format(eStmt.getExpression(), document); + } + + /** var,let,const */ + void _format(VariableStatement vStmt, IFormattableDocument document) { + + configureModifiers(vStmt, document); + + // "let", "var" or "const" + document.append(textRegionExtensions.regionFor(vStmt) + .feature(N4JSPackage.Literals.VARIABLE_DECLARATION_CONTAINER__VAR_STMT_KEYWORD), hrf -> hrf.oneSpace()); + + configureCommas(vStmt, document); + + interiorBUGFIX(vStmt, hrf -> hrf.indent(), document); // vStmt.interior, hrf->hrf.indent(); + int lastIdx = vStmt.getVarDeclsOrBindings().size() - 1; + + for (int i = 0; i < vStmt.getVarDeclsOrBindings().size(); i++) { + VariableDeclarationOrBinding e = vStmt.getVarDeclsOrBindings().get(i); + document.format(e); + + if (i > 0) { // assignments start in separate lines. + if (e instanceof VariableDeclaration) { + if (e.getExpression() != null) { + document.prepend(e, hrf -> hrf.newLine()); + } else { + document.prepend(e, hrf -> { + hrf.setNewLines(0, 1, 1); + hrf.lowPriority(); + }); + } + } else if (e instanceof VariableBinding) { + if (e.getExpression() != null) { + document.prepend(e, hrf -> hrf.newLine()); + } else { + document.prepend(e, hrf -> { + hrf.setNewLines(0, 1, 1); + hrf.lowPriority(); + }); + } + } + } + + if (i < lastIdx) { + // assignments start let following continue in separate lines. + if (e instanceof VariableDeclaration) { + if (e.getExpression() != null) { + document.append(textRegionExtensions.immediatelyFollowing(e).keyword(","), + hrf -> hrf.newLine()); + } else { + document.prepend(e, hrf -> { + hrf.setNewLines(0, 1, 1); + hrf.lowPriority(); + }); + } + + } else if (e instanceof VariableBinding) { + if (e.getExpression() != null) { + document.append(textRegionExtensions.immediatelyFollowing(e).keyword(","), + hrf -> hrf.newLine()); + } else { + document.prepend(e, hrf -> { + hrf.setNewLines(0, 1, 1); + hrf.lowPriority(); + }); + } + } + } + } + } + + void _format(VariableDeclaration vDecl, IFormattableDocument document) { + document.set(textRegionExtensions.previousHiddenRegion(vDecl), hrf -> hrf.oneSpace()); + document.surround(textRegionExtensions.regionFor(vDecl).keyword("="), hrf -> hrf.oneSpace()); + document.format(vDecl.getExpression()); + document.format(vDecl.getDeclaredTypeRefInAST()); + } + + void _format(VariableBinding vBind, IFormattableDocument document) { + document.set(textRegionExtensions.previousHiddenRegion(vBind), hrf -> hrf.oneSpace()); + document.surround(textRegionExtensions.regionFor(vBind).keyword("="), hrf -> hrf.oneSpace()); + document.format(vBind.getPattern()); + document.format(vBind.getExpression()); + document.format(vBind.getPattern()); + } + + void _format(BindingPattern bp, IFormattableDocument document) { + // ObjectBindingPattern + // ArrayBindingPattern + + // '{' or '[' + document.append(head(textRegionExtensions.semanticRegions(bp)), hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + hrf.autowrap(); + }); + document.prepend(last(textRegionExtensions.semanticRegions(bp)), hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + hrf.autowrap(); + }); + configureCommas(bp, document); // doesn't handle elision. + + for (EObject eo : bp.eContents()) { + format(eo, document); + } + } + + void _format(ThrowStatement thrStmt, IFormattableDocument document) { + // No autowrap, otherwise ASI + document.prepend(thrStmt.getExpression(), hrf -> { + hrf.setNewLines(0, 0, 0); + hrf.oneSpace(); + }); + document.format(thrStmt.getExpression()); + + } + + void _format(CatchBlock ctch, IFormattableDocument document) { + document.prepend(ctch, hrf -> { + hrf.setNewLines(0, 0, 0); + hrf.oneSpace(); + }); + document.format(ctch.getCatchVariable()); + document.format(ctch.getBlock()); + + } + + void _format(FinallyBlock finlly, IFormattableDocument document) { + document.set(textRegionExtensions.previousHiddenRegion(finlly), hrf -> { + hrf.setNewLines(1); + hrf.oneSpace(); + }); + document.format(finlly.getBlock()); + } + + /** + * Insert one space in front of first '{' in the direct content of the element. semEObject is a semanticObject, e.g. + * N4EnumDecl, N4Classifier ... + */ + private void insertSpaceInFrontOfCurlyBlockOpener(EObject semEObject, IFormattableDocument document) { + document.prepend(textRegionExtensions.regionFor(semEObject).keyword("{"), hrf -> hrf.oneSpace()); + } + + /** force: " @" and no newLine after '@' */ + private void noSpaceAfterAT(EObject semEObject, IFormattableDocument document) { + document.prepend(document.append(textRegionExtensions.regionFor(semEObject).keyword("@"), hrf -> { + hrf.noSpace(); + hrf.setNewLines(1); + }), hrf -> hrf.oneSpace()); + } + + /** On the direct level of an semantic Object enforce commas to ", " with autoWrap option. */ + private void configureCommas(EObject semEObject, IFormattableDocument document) { + for (ISemanticRegion sr : textRegionExtensions.regionFor(semEObject).keywords(",")) { + document.prepend(sr, hrf -> hrf.noSpace()); + document.append(sr, hrf -> { + hrf.oneSpace(); + hrf.autowrap(); + }); + } + } + + void indentExcludingAnnotations(EObject semObject, IFormattableDocument document) { + // Exclude Annotations from indentation field.interior, hrf->hrf.indent(); + ISemanticRegion begin = findFirst(textRegionExtensions.semanticRegions(semObject), + sr -> !(sr instanceof Annotation)); + ISemanticRegion end = last(textRegionExtensions.semanticRegions(semObject)); + + if (begin != end) { // guard to prevent wrong indentation + document.interior(begin, end, hrf -> hrf.indent()); + } + } + + private void _configureAnnotations(AnnotableN4MemberDeclaration semEObject, IFormattableDocument document) { + configureAnnotations(semEObject.getAnnotationList(), document); + } + + @SuppressWarnings("unused") + private void _configureAnnotations(AnnotablePropertyAssignment semEObject, IFormattableDocument document) { + configureAnnotations(semEObject.getAnnotationList(), document); + } + + private void _configureAnnotations(AnnotableScriptElement semEObject, IFormattableDocument document) { + configureAnnotations(semEObject.getAnnotationList(), document); + } + + private void _configureAnnotations(AnnotableExpression semEObject, IFormattableDocument document) { + configureAnnotations(semEObject.getAnnotationList(), document); + } + + private void _configureAnnotations(AbstractAnnotationList aList, IFormattableDocument document) { + if (aList == null || aList.getAnnotations().isEmpty()) { + return; + } + + // TODO in case of trapped in Annotation like 'export @Final public class A{}' - a reorder would be necessary + // (see format for export) + document.prepend(aList, hrf -> { + hrf.setNewLines(2, 2, 2); + hrf.highPriority(); + }); + + // TODO special annotations like @Internal ? --> together with public, reorder to be in same line? + document.append(aList, hrf -> hrf.newLine()); + + for (int idx = 0; idx < aList.getAnnotations().size(); idx++) { + Annotation it = aList.getAnnotations().get(idx); + configureAnnotation(it, document, true, idx == 0); + } + } + + /** + * + * @param withLineWraps + * true do line-wrapping + * @param isFirstAnnotation + * if this is the first annotation in a sequence ( used with line-wrapping in front of '@') + */ + private void configureAnnotation(Annotation ann, IFormattableDocument document, boolean withLineWraps, + boolean isFirstAnnotation) { + // configure arguments + Pair parens = textRegionExtensions.regionFor(ann).keywordPairs("(", ")") + .get(0); + if (parens != null) { + document.append(document.prepend(parens.getKey(), hrf -> hrf.noSpace()), hrf -> hrf.noSpace()); + document.append(document.prepend(parens.getValue(), hrf -> hrf.noSpace()), hrf -> { + if (withLineWraps) { + hrf.noSpace(); + hrf.setNewLines(1); + } else { + hrf.oneSpace(); + hrf.setNewLines(0); + } + }); + document.interior(parens, hrf -> hrf.indent()); + // line break before "@": + if (withLineWraps && !isFirstAnnotation) { + document.prepend(parens.getKey().getPreviousSemanticRegion().getPreviousSemanticRegion(), + hrf -> hrf.setNewLines(1)); + } + + configureCommas(ann, document); + } + + // Configure @-Syntax + // Special case here: for "@XY" we can either get "@" or "XY" as the first semantic element + ISemanticRegion sr = head(textRegionExtensions.semanticRegions(ann)); + if (sr.getGrammarElement() instanceof Keyword) { + // assume '@' + document.append(sr, hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }); + } else { + // for "@Final" "Final" will be the first semantic region in case of exported classes, + document.prepend(sr, hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }); + } + } + + @SuppressWarnings("unused") + private void _configureAnnotations(Object semEObject, IFormattableDocument document) { + // no annotations to be configured. + } + + @SuppressWarnings("unused") + private void _configureAnnotations(Void x, IFormattableDocument document) { + // no annotations to be configured. + } + + private void configureAnnotationsInLine(FormalParameter fpar, IFormattableDocument document) { + if (fpar.getAnnotations().isEmpty()) { + return; + } + // (@x @y("") bogus a:typ) + Annotation fann = fpar.getAnnotations().get(0); + + configureAnnotation(fann, document, false, true); + document.prepend(fann, hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + hrf.autowrap(); + }); + + for (Annotation ann : tail(fpar.getAnnotations())) { + configureAnnotation(ann, document, false, false); + document.prepend(ann, hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + hrf.autowrap(); + }); + } + + document.append(last(fpar.getAnnotations()), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + hrf.autowrap(); + }); + } + + /** only script-level annotations '@@' */ + private void formatScriptAnnotations(Script script, IFormattableDocument document) { + if (script.getAnnotations().isEmpty()) { + return; + } + + if (textRegionExtensions.previousHiddenRegion(script.getAnnotations().get(0)).containsComment()) { + document.prepend(script.getAnnotations().get(0), hrf -> hrf.noSpace()); + } else { + document.prepend(script.getAnnotations().get(0), hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }); + } + document.append(last(script.getAnnotations()), hrf -> hrf.setNewLines(2, 2, 2)); + + for (int idx = 0; idx < script.getAnnotations().size(); idx++) { + Annotation it = script.getAnnotations().get(idx); + if (idx != 0) { + document.prepend(it, hrf -> { + hrf.setNewLines(1); + hrf.noSpace(); + }); + } + // its an '@@' + ISemanticRegion sr = head(textRegionExtensions.semanticRegions(it)); + document.append(sr, hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }); + } + } + + @Override + public ITextReplacer createCommentReplacer(IComment comment) { + // Overridden to distinguish between JSDOC-style, standard ML, formatter-off ML-comment. + EObject grammarElement = comment.getGrammarElement(); + if (grammarElement instanceof AbstractRule) { + String ruleName = ((AbstractRule) grammarElement).getName(); + if (ruleName.startsWith("ML")) { + String cText = comment.getText(); + if (cText.startsWith("/**") && !cText.startsWith("/***")) { // JSDOC + return new N4MultilineCommentReplacer(comment, '*'); + } else if (cText.startsWith("/*-")) { // Turn-off formatting. + return new OffMultilineCommentReplacer(comment, !isNotFirstInLine(comment)); + } else { // All other + return new FixedMultilineCommentReplacer(comment); + } + } + if (ruleName.startsWith("SL")) { + if (isNotFirstInLine(comment)) { + return new SinglelineDocCommentReplacer(comment, "//"); + } else { + return new SinglelineCodeCommentReplacer(comment, "//"); + } + } + } + + // fall back to super-impl. + return super.createCommentReplacer(comment); + } + + private static boolean isNotFirstInLine(IComment comment) { + ILineRegion lineRegion = comment.getLineRegions().get(0); + + return !comment.contains(lineRegion.getOffset()); + } + + @Override + public IndentHandlingTextReplaceMerger createTextReplacerMerger() { + return new IndentHandlingTextReplaceMerger(this); + } + + /** DEBUG-helper */ + private static String containmentStructure(EObject eo) { + String name = eo.getClass().getSimpleName(); + if (eo.eContainer() != null) { + return containmentStructure(eo.eContainer()) + "." + eo.eContainingFeature().getName() + "->" + name; + } + return name; + } + + /** HELPER to avoid Warnings in code, since @SuppressWarnings("unused") is not active in xtend code. */ + @SuppressWarnings("unused") + int suppressUnusedWarnings(Object... e) { + return PRIO_4; + } + + /** + * Simple tracker that only gives exactly one time the value {@code true} when calling + * {@link StateTrack#shouldDoThenDone()} + */ + private final static class StateTrack { + private boolean done = false; + + /** + * This method returns {@code true} exactly on it's first invocation. Proceeding calls always return + * {@code false}. + * + * @return Returns {@code true} if not done, immediately switches {@link #done} to {@code true}; returns + * {@code false} if already done. + */ + boolean shouldDoThenDone() { + boolean ret = !done; + done = true; + return ret; + } + } + + /**************************************************************************************************************** + * + * Type Expression + * + ***************************************************************************************************************/ + void _format(UnionTypeExpression ute, IFormattableDocument document) { + for (ISemanticRegion sr : textRegionExtensions.regionFor(ute).keywords("|")) { + document.prepend(document.surround(sr, hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + }), hrf -> { + hrf.autowrap(); + hrf.highPriority(); + }); + } + + for (TypeRef tr : ute.getTypeRefs()) { + format(tr, document); + } + + // OLD syntax: + ISemanticRegion kwUnion = textRegionExtensions.regionFor(ute).keyword("union"); + if (kwUnion != null) { + ISequentialRegion sr = document.append(document.prepend(kwUnion, hrf -> hrf.oneSpace()), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + }); + + /* '{' */ + document.append(sr.getNextSemanticRegion(), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + }); + /* '}' */ + document.prepend(last(textRegionExtensions.semanticRegions(ute)), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + }); + } + } + + void _format(IntersectionTypeExpression ite, IFormattableDocument document) { + for (ISemanticRegion sr : textRegionExtensions.regionFor(ite).keywords("&")) { + document.prepend(document.surround(sr, hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + }), hrf -> { + hrf.autowrap(); + hrf.highPriority(); + }); + } + + for (TypeRef tr : ite.getTypeRefs()) { + format(tr, document); + } + // OLD syntax + ISemanticRegion kwInersection = textRegionExtensions.regionFor(ite).keyword("intersection"); + if (kwInersection != null) { + ISequentialRegion sr = document.append(document.prepend(kwInersection, hrf -> hrf.oneSpace()), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + }); + /* '{' */ + document.append(sr.getNextSemanticRegion(), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + }); + /* '}' */ + document.prepend(last(textRegionExtensions.semanticRegions(ite)), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + }); + } + } + + void _format(TStructMember tsm, IFormattableDocument document) { + if (tsm instanceof TField) { + configureOptionality((TField) tsm, document); + } else if (tsm instanceof org.eclipse.n4js.ts.types.FieldAccessor) { + configureGetSetKeyword((org.eclipse.n4js.ts.types.FieldAccessor) tsm, document); + configureOptionality((org.eclipse.n4js.ts.types.FieldAccessor) tsm, document); + + Pair parenPair = textRegionExtensions.regionFor(tsm) + .keywordPairs("(", ")").get(0); + document.append(document.prepend(parenPair.getKey(), hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }), hrf -> hrf.noSpace()); + } + // get, set, method, field + + for (EObject eo : tsm.eContents()) { + format(eo, document); + } + // TODO format TStruct* more thoroughly + // (note: added some TStructMember formatting while working on IDE-2405, but it is still incomplete!) + } + + private void configureUndefModifier(StaticBaseTypeRef sbtr, IFormattableDocument document) { + // UndefModifier "?" + document.prepend(textRegionExtensions.regionFor(sbtr) + .feature(TypeRefsPackage.Literals.TYPE_REF__FOLLOWED_BY_QUESTION_MARK), hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }); + } + + void _format(ThisTypeRef ttr, IFormattableDocument document) { + configureUndefModifier(ttr, document); + if (ttr instanceof ThisTypeRefStructural) { + interiorBUGFIX(ttr, hrf -> hrf.indent(), document); + configureStructuralAdditions(ttr, document); + for (EObject eo : ttr.eContents()) { + format(eo, document); + } + } + } + + void _format(ParameterizedTypeRef ptr, IFormattableDocument document) { + interiorBUGFIX(ptr, hrf -> hrf.indent(), document); // ptr.interior, hrf->hrf.indent(); + configureUndefModifier(ptr, document); + + // Union / Intersection + for (ISemanticRegion sr : textRegionExtensions.regionFor(ptr).keywords("&", "|")) { + document.append(document.surround(sr, hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + }), hrf -> { + hrf.autowrap(); + hrf.highPriority(); + }); + } + // Short-Hand Syntax for Arrays + if (ptr.isArrayTypeExpression()) { + document.append(textRegionExtensions.regionFor(ptr).keyword("["), hrf -> hrf.noSpace()); + document.append(textRegionExtensions.regionFor(ptr).keyword("]"), hrf -> hrf.noSpace()); + } + // Short-Hand Syntax for IterableN + if (ptr.isArrayNTypeExpression()) { + document.append(textRegionExtensions.regionFor(ptr).keyword("["), hrf -> hrf.noSpace()); + document.append(textRegionExtensions.regionFor(ptr).keyword("]"), hrf -> hrf.noSpace()); + } + formatTypeArguments(ptr, document); + + // ParameterizedTypeRefStructural : + configureStructuralAdditions(ptr, document); + + // generically format content: + for (EObject eo : ptr.eContents()) { + format(eo, document); + } + } + + /** used for "~X with {}" except for the 'X' part. */ + void configureStructuralAdditions(TypeRef ptr, IFormattableDocument document) { + ISemanticRegion semRegTypingStrategy = textRegionExtensions.regionFor(ptr) + .ruleCallTo(grammarAccess.getTypingStrategyUseSiteOperatorRule()); + if (semRegTypingStrategy != null) { + document.append(document.prepend(semRegTypingStrategy, hrf -> hrf.oneSpace()), hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }); + + // declaredType + document.append(semRegTypingStrategy.getNextSemanticRegion(), null); + ISemanticRegion kwWith = textRegionExtensions.regionFor(ptr).keyword("with"); + + if (kwWith != null) { + document.surround(kwWith, hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + hrf.autowrap(); + }); + + Pair bracesPair = textRegionExtensions.regionFor(ptr) + .keywordPairs("{", "}").get(0); + + document.append(bracesPair.getKey(), hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + hrf.autowrap(); + }); + + document.prepend(bracesPair.getValue(), hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + hrf.autowrap(); + }); + + // textRegionExtensions.regionFor(ptr).keywords(",",";").forEach[ + // prepend[hrf.noSpace();hrf.setNewLines(1);].append[hrf.oneSpace();hrf.setNewLines(1); hrf.autowrap(); + // } ] + for (ISemanticRegion sr : textRegionExtensions.regionFor(ptr).keywords(";")) { + document.append(document.prepend(sr, hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + hrf.autowrap(); + hrf.lowPriority(); + }); + } + + for (TStructMember sm : tail((((StructuralTypeRef) ptr).getAstStructuralMembers()))) { + document.set(textRegionExtensions.regionForEObject(sm).getPreviousHiddenRegion(), hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + hrf.autowrap(); + }); + } + } + } + } + + /** formats type argument section including outside border. */ + void formatTypeArguments(ParameterizedTypeRef semObject, IFormattableDocument document) { + if (semObject.getDeclaredTypeArgs().isEmpty()) { + return; + } + // to "<": + document.prepend(document.append(textRegionExtensions.regionFor(semObject).keyword("<"), + hrf -> hrf.noSpace()), + hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + hrf.lowPriority(); + }); + document.append(document.prepend(textRegionExtensions.regionFor(semObject).keyword(">"), + hrf -> hrf.noSpace()), + hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + hrf.lowPriority(); + }); + for (TypeArgument typeArg : semObject.getDeclaredTypeArgs()) { + document.append(textRegionExtensions.immediatelyFollowing(document.append(typeArg, hrf -> hrf.noSpace())) + .keyword(","), hrf -> hrf.oneSpace()); + format(typeArg, document); + } + + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Configure Methods + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + private void configureGetSetKeyword(FieldAccessor fieldAccessor, IFormattableDocument document) { + String kw = (fieldAccessor instanceof GetterDeclaration) ? "get" : "set"; + + document.append(document.prepend(textRegionExtensions.regionFor(fieldAccessor).keyword(kw), + hrf -> hrf.oneSpace()), + hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + hrf.autowrap(); + }); + } + + private void configureGetSetKeyword(org.eclipse.n4js.ts.types.FieldAccessor tFieldAccessor, + IFormattableDocument document) { + + String kw = (tFieldAccessor instanceof TGetter) ? "get" : "set"; + document.append(document.prepend(textRegionExtensions.regionFor(tFieldAccessor).keyword(kw), + hrf -> hrf.oneSpace()), + hrf -> { + hrf.oneSpace(); + hrf.setNewLines(0); + hrf.autowrap(); + }); + } + + private void configureOptionality(N4FieldDeclaration fieldDecl, IFormattableDocument document) { + document.prepend( + textRegionExtensions.regionFor(fieldDecl) + .feature(N4JSPackage.Literals.N4_FIELD_DECLARATION__DECLARED_OPTIONAL), + hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }); + } + + private void configureOptionality(FieldAccessor fieldAccessor, IFormattableDocument document) { + document.prepend( + textRegionExtensions.regionFor(fieldAccessor) + .feature(N4JSPackage.Literals.FIELD_ACCESSOR__DECLARED_OPTIONAL), + hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }); + } + + private void configureOptionality(TField tField, IFormattableDocument document) { + document.prepend( + textRegionExtensions.regionFor(tField).feature(TypesPackage.Literals.TFIELD__OPTIONAL), + hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }); + } + + private void configureOptionality(org.eclipse.n4js.ts.types.FieldAccessor tFieldAccessor, + IFormattableDocument document) { + document.prepend( + textRegionExtensions.regionFor(tFieldAccessor).feature(TypesPackage.Literals.FIELD_ACCESSOR__OPTIONAL), + hrf -> { + hrf.noSpace(); + hrf.setNewLines(0); + }); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Bug-workarounds + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * Temporarily used method to replace document.interior(EObject, Procedure1) to prevent wrong indentations. + * + * Main pattern replace document-extension method call: + * + *
+	 * object.interior, hrf->hrf.indent()
+	 * 
+ * + * by + * + *
+	 * object.interiorBUGFIX(, hrf->hrf.indent(),document);
+	 *
+	 * 
+	 *
+	 */
+	void interiorBUGFIX(EObject object, Procedure1 init,
+			IFormattableDocument document) {
+
+		IEObjectRegion objRegion = getTextRegionAccess().regionForEObject(object);
+		if (objRegion != null) {
+			IHiddenRegion previous = objRegion.getPreviousHiddenRegion();
+			IHiddenRegion next = objRegion.getNextHiddenRegion();
+			if (previous != null && next != null && previous != next) {
+				ISemanticRegion nsr = previous.getNextSemanticRegion();
+				ISemanticRegion psr = next.getPreviousSemanticRegion();
+				if (nsr != psr) { // standard
+									// case
+					document.interior(nsr, psr, init);
+				} else {
+					// former error-case:
+					// there is no interior --> don't do anything!
+					//
+					// applying to the next HiddenRegion is a bad idea,
+					// since it could wrongly indent a multiline-comment (c.f. GHOLD-260)
+				}
+			}
+		}
+	}
+
+	/**
+	 * Dummy method to prevent accidentally calling interior - extension method form document. You should call
+	 * interiorBUGFIX instead !
+	 */
+	@SuppressWarnings("unused")
+	Procedure1 interior(EObject eo, Procedure1 init) {
+		throw new IllegalStateException("Method should not be called.");
+	}
+
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.xtend
deleted file mode 100644
index 9ca9a39a56..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatter.xtend
+++ /dev/null
@@ -1,1441 +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.formatting2;
-
-import com.google.inject.Inject
-import org.eclipse.emf.common.util.EList
-import org.eclipse.emf.ecore.EObject
-import org.eclipse.n4js.n4JS.AbstractAnnotationList
-import org.eclipse.n4js.n4JS.AbstractCaseClause
-import org.eclipse.n4js.n4JS.AdditiveExpression
-import org.eclipse.n4js.n4JS.AnnotableExpression
-import org.eclipse.n4js.n4JS.AnnotableN4MemberDeclaration
-import org.eclipse.n4js.n4JS.AnnotablePropertyAssignment
-import org.eclipse.n4js.n4JS.AnnotableScriptElement
-import org.eclipse.n4js.n4JS.Annotation
-import org.eclipse.n4js.n4JS.ArrayLiteral
-import org.eclipse.n4js.n4JS.ArrowFunction
-import org.eclipse.n4js.n4JS.AssignmentExpression
-import org.eclipse.n4js.n4JS.AwaitExpression
-import org.eclipse.n4js.n4JS.BinaryBitwiseExpression
-import org.eclipse.n4js.n4JS.BinaryLogicalExpression
-import org.eclipse.n4js.n4JS.BindingPattern
-import org.eclipse.n4js.n4JS.Block
-import org.eclipse.n4js.n4JS.BooleanLiteral
-import org.eclipse.n4js.n4JS.CastExpression
-import org.eclipse.n4js.n4JS.CatchBlock
-import org.eclipse.n4js.n4JS.CommaExpression
-import org.eclipse.n4js.n4JS.ConditionalExpression
-import org.eclipse.n4js.n4JS.EqualityExpression
-import org.eclipse.n4js.n4JS.ExportDeclaration
-import org.eclipse.n4js.n4JS.Expression
-import org.eclipse.n4js.n4JS.ExpressionStatement
-import org.eclipse.n4js.n4JS.FieldAccessor
-import org.eclipse.n4js.n4JS.FinallyBlock
-import org.eclipse.n4js.n4JS.ForStatement
-import org.eclipse.n4js.n4JS.FormalParameter
-import org.eclipse.n4js.n4JS.FunctionDeclaration
-import org.eclipse.n4js.n4JS.FunctionDefinition
-import org.eclipse.n4js.n4JS.FunctionExpression
-import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor
-import org.eclipse.n4js.n4JS.GenericDeclaration
-import org.eclipse.n4js.n4JS.GetterDeclaration
-import org.eclipse.n4js.n4JS.IdentifierRef
-import org.eclipse.n4js.n4JS.IfStatement
-import org.eclipse.n4js.n4JS.ImportDeclaration
-import org.eclipse.n4js.n4JS.IndexedAccessExpression
-import org.eclipse.n4js.n4JS.IntLiteral
-import org.eclipse.n4js.n4JS.JSXElement
-import org.eclipse.n4js.n4JS.MultiplicativeExpression
-import org.eclipse.n4js.n4JS.N4ClassDeclaration
-import org.eclipse.n4js.n4JS.N4EnumDeclaration
-import org.eclipse.n4js.n4JS.N4FieldDeclaration
-import org.eclipse.n4js.n4JS.N4InterfaceDeclaration
-import org.eclipse.n4js.n4JS.N4JSPackage
-import org.eclipse.n4js.n4JS.N4SetterDeclaration
-import org.eclipse.n4js.n4JS.N4TypeVariable
-import org.eclipse.n4js.n4JS.NamedImportSpecifier
-import org.eclipse.n4js.n4JS.NamespaceImportSpecifier
-import org.eclipse.n4js.n4JS.NewExpression
-import org.eclipse.n4js.n4JS.NullLiteral
-import org.eclipse.n4js.n4JS.ObjectLiteral
-import org.eclipse.n4js.n4JS.ParameterizedCallExpression
-import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression
-import org.eclipse.n4js.n4JS.ParenExpression
-import org.eclipse.n4js.n4JS.PostfixExpression
-import org.eclipse.n4js.n4JS.PromisifyExpression
-import org.eclipse.n4js.n4JS.RegularExpressionLiteral
-import org.eclipse.n4js.n4JS.RelationalExpression
-import org.eclipse.n4js.n4JS.ReturnStatement
-import org.eclipse.n4js.n4JS.Script
-import org.eclipse.n4js.n4JS.ShiftExpression
-import org.eclipse.n4js.n4JS.StringLiteral
-import org.eclipse.n4js.n4JS.SuperLiteral
-import org.eclipse.n4js.n4JS.SwitchStatement
-import org.eclipse.n4js.n4JS.TaggedTemplateString
-import org.eclipse.n4js.n4JS.TemplateLiteral
-import org.eclipse.n4js.n4JS.TemplateSegment
-import org.eclipse.n4js.n4JS.ThisLiteral
-import org.eclipse.n4js.n4JS.ThrowStatement
-import org.eclipse.n4js.n4JS.UnaryExpression
-import org.eclipse.n4js.n4JS.UnaryOperator
-import org.eclipse.n4js.n4JS.VariableBinding
-import org.eclipse.n4js.n4JS.VariableDeclaration
-import org.eclipse.n4js.n4JS.VariableStatement
-import org.eclipse.n4js.n4JS.YieldExpression
-import org.eclipse.n4js.services.N4JSGrammarAccess
-import org.eclipse.n4js.ts.typeRefs.IntersectionTypeExpression
-import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef
-import org.eclipse.n4js.ts.typeRefs.StaticBaseTypeRef
-import org.eclipse.n4js.ts.typeRefs.StructuralTypeRef
-import org.eclipse.n4js.ts.typeRefs.ThisTypeRef
-import org.eclipse.n4js.ts.typeRefs.ThisTypeRefStructural
-import org.eclipse.n4js.ts.typeRefs.TypeRef
-import org.eclipse.n4js.ts.typeRefs.TypeRefsPackage
-import org.eclipse.n4js.ts.typeRefs.UnionTypeExpression
-import org.eclipse.n4js.ts.types.TField
-import org.eclipse.n4js.ts.types.TGetter
-import org.eclipse.n4js.ts.types.TStructMember
-import org.eclipse.n4js.ts.types.TypesPackage
-import org.eclipse.xtext.AbstractRule
-import org.eclipse.xtext.Keyword
-import org.eclipse.xtext.formatting2.IAutowrapFormatter
-import org.eclipse.xtext.formatting2.IFormattableDocument
-import org.eclipse.xtext.formatting2.IHiddenRegionFormatter
-import org.eclipse.xtext.formatting2.IHiddenRegionFormatting
-import org.eclipse.xtext.formatting2.ITextReplacer
-import org.eclipse.xtext.formatting2.internal.SinglelineCodeCommentReplacer
-import org.eclipse.xtext.formatting2.internal.SinglelineDocCommentReplacer
-import org.eclipse.xtext.formatting2.regionaccess.IComment
-import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion
-import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion
-import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion
-import org.eclipse.xtext.formatting2.regionaccess.ITextSegment
-import org.eclipse.xtext.xbase.lib.Procedures.Procedure1
-import org.eclipse.xtext.xtext.generator.parser.antlr.splitting.simpleExpressions.NumberLiteral
-
-import static org.eclipse.n4js.formatting2.N4JSFormatterPreferenceKeys.*
-import static org.eclipse.n4js.formatting2.N4JSGenericFormatter.*
-
-class N4JSFormatter extends TypeExpressionsFormatter {
-
-	/** Debug switch */
-	private static var debug = false;
-
-	@Inject extension N4JSGrammarAccess
-
-	/** PRIO_4 = -7 - still very low.
-	 * Standard priorities in the formatter are:
-	 * 
    - *
  • lowPriority = -1; == PRIO_10 - *
  • normal priority = 0; == PRIO_11 - *
  • high priority = +1; == PRIO_12 - *
- */ - static val PRIO_4 = PRIO_3 + 1; - static val PRIO_13 = IHiddenRegionFormatter.HIGH_PRIORITY + 1; - - - private def maxConsecutiveNewLines() { - // let's stick to 2 - getPreference(FORMAT_MAX_CONSECUTIVE_NEWLINES); - } - - - def dispatch void format(Script script, extension IFormattableDocument document) { - - val extension generic = new N4JSGenericFormatter(_n4JSGrammarAccess, textRegionExtensions) - if (getPreference(FORMAT_PARENTHESIS)) { -// script.formatParenthesisBracketsAndBraces(document) - } - - // TODO the following line requires more conflict handling with semicolons: - // script.interior[noIndentation;]; - - script.formatSemicolons(document); - script.formatColon(document); - - formatScriptAnnotations(script,document); - - for (element : script.scriptElements) { - // element.append[setNewLines(1, 1, maxConsecutiveNewLines);noSpace; autowrap].prepend[noSpace]; - element.append[setNewLines(1, 1, maxConsecutiveNewLines); autowrap]; - element.format; - } - - // format last import, overrides default newLines: - script.scriptElements.filter(ImportDeclaration).last?.append[setNewLines(2, 2, 3); highPriority]; - - } - - /** put modifiers into a single line separated by one space, last modifier has one space to following element. */ - def void configureModifiers(EObject semObject, extension IFormattableDocument document){ - semObject.regionFor.ruleCallsTo( n4ModifierRule ).forEach[ - append[oneSpace]; - ]; - } - - def void configureTypingStrategy(EObject semObject, extension IFormattableDocument document) { - semObject.regionFor.ruleCallsTo( typingStrategyDefSiteOperatorRule, typingStrategyUseSiteOperatorRule).forEach[ - append[noSpace]; - ] - } - - def void formatTypeVariables(GenericDeclaration semObject, extension IFormattableDocument document) { - if( semObject.typeVars.isEmpty ) return; - // to "<": - semObject.regionFor.keyword("<").prepend[oneSpace;newLines=0].append[noSpace]; - semObject.regionFor.keyword(">").prepend[noSpace;newLines=0]; - for( typeVar: semObject.typeVars ){ - typeVar.append[noSpace].immediatelyFollowing.keyword(",").append[oneSpace]; - typeVar.format(document); - } - - } - - def dispatch format(N4ClassDeclaration clazz, extension IFormattableDocument document) { - - clazz.configureAnnotations(document); - clazz.insertSpaceInFrontOfCurlyBlockOpener(document); - clazz.indentExcludingAnnotations(document); - - - clazz.configureTypingStrategy(document); - clazz.configureModifiers(document); - - clazz.formatTypeVariables(document); - -// val semRegModifier = clazz.regionFor.feature( N4JSPackage.Literals.MODIFIABLE_ELEMENT__DECLARED_MODIFIERS); -// if( semRegModifier !== null ) { // only if exists. -// val beginModifierHR = semRegModifier.previousHiddenRegion; -// val endModifierHR = semRegModifier.nextHiddenRegion; -// // go over all semantic regions in the modifier location. -// var currHR = beginModifierHR.nextHiddenRegion; -// while( currHR != endModifierHR ){ -// currHR.set[oneSpace]; -// currHR = currHR.nextHiddenRegion; -// } -// endModifierHR.set[oneSpace]; -// } // end modifier formatting TODO extract into method. - - // TODO revise the following pattern of call-back implementations. - // Define lambda for callback & normal use: - val twolinesBeforeFirstMember = [int prio | clazz.ownedMembersRaw.head.prepend[newLines=2;priority = prio]]; - - // Defines CallBack for autoWrap: - val callBackOnAutoWrap = new IAutowrapFormatter{ // callback for auto-wrapping with implements - var didReconfigure = false; // track to only execute once. - override format(ITextSegment region, IHiddenRegionFormatting wrapped, IFormattableDocument document) { - if( !didReconfigure ) { - twolinesBeforeFirstMember.apply(IHiddenRegionFormatter.HIGH_PRIORITY); // reformat with higher priority - didReconfigure=true; // keep state. - } - } - }; - - // 2nd version of implementing the callback: - val StateTrack state2 = new StateTrack; - val IAutowrapFormatter callBackOnAutoWrap2 = [region, hrFormatting, document2 | - if( state2.shouldDoThenDone ) twolinesBeforeFirstMember.apply(IHiddenRegionFormatter.HIGH_PRIORITY); - ]; - suppressUnusedWarnings( callBackOnAutoWrap2 ); - - // Allow for lineBreaks in front of keywords: - clazz.regionFor.keyword("extends").prepend[ - setNewLines(0,0,1); // allow line break in front. - autowrap; - ].append[oneSpace; autowrap;]; - - clazz.regionFor.keyword("implements").prepend[ - setNewLines(0,0,1); - autowrap; - priority = IHiddenRegionFormatter.LOW_PRIORITY; - onAutowrap=callBackOnAutoWrap; - ].append[oneSpace; autowrap;]; - - clazz.implementedInterfaceRefs.tail.forEach[prepend[ - autowrap; - priority = IHiddenRegionFormatter.LOW_PRIORITY; - onAutowrap=callBackOnAutoWrap; - ]]; - - // special case if the header of the class spans multiple lines, then insert extra line break. - val kwClass = clazz.regionFor.keyword("class"); - val kwBrace = clazz.regionFor.keyword("{"); // autowrap-listener ? - if( ! kwClass.lineRegions.head.contains( kwBrace ) ) { - twolinesBeforeFirstMember.apply(IHiddenRegionFormatter.NORMAL_PRIORITY); - } else { - clazz.ownedMembersRaw.head.prepend[setNewLines(1,1,maxConsecutiveNewLines);autowrap;]; - } - - kwClass.append[oneSpace]; - - for (member : clazz.ownedMembersRaw) { - member.append[setNewLines(1, 1, maxConsecutiveNewLines)] - member.format - } - - // Collapse empty block: - if( clazz.ownedMembersRaw.isEmpty) { - // Empty body: - kwBrace.append[noSpace;newLines=0]; - } - } - - def dispatch format(N4InterfaceDeclaration interf, extension IFormattableDocument document) { - interf.configureAnnotations(document); - interf.configureModifiers(document); - interf.insertSpaceInFrontOfCurlyBlockOpener(document); - interf.indentExcludingAnnotations(document);// .interiorBUGFIX([indent],document); //interf.interior[indent]; - - interf.ownedMembersRaw.head.prepend[setNewLines(1, 1, maxConsecutiveNewLines)] - for (member : interf.ownedMembersRaw) { - member.append[setNewLines(1, 1, maxConsecutiveNewLines)] - member.format - } - } - -// def dispatch void format(N4MemberDeclaration member, extension IFormattableDocument document) { -// member.regionFor.keyword("(").prepend[noSpace; newLines=0].append[noSpace] -// -// member.insertSpaceInfrontOfPropertyNames(document); -// for (c : member.eContents) { -// c.format; -// } -// } - - def dispatch void format(N4FieldDeclaration field, extension IFormattableDocument document) { - field.configureAnnotations(document); - field.configureModifiers(document); - - field.indentExcludingAnnotations(document); - - field.configureOptionality(document); - field.regionFor.keyword("=").prepend[oneSpace].append[oneSpace]; - field.expression.format; - field.declaredTypeRefInAST.format; - } - - - - -// def dispatch format(N4MethodDeclaration method, extension IFormattableDocument document) { -// method.configureAnnotations(document); -// method.insertSpaceInfrontOfPropertyNames(document); -// -// method.regionFor.keyword("(").prepend[noSpace; newLines=0] -// -// method.body.regionFor.keyword("{").prepend[oneSpace; newLines = 0] -// for (child : method.eContents) { -// child.format; -// } -// } -// - def dispatch void format(FunctionExpression funE, extension IFormattableDocument document) { - funE.configureAnnotations(document); - funE.configureModifiers(document); - if (funE.isArrowFunction) { - throw new IllegalStateException("Arrow functions should be formated differently.") - } - funE.fpars.configureFormalParameters(document,[/*n.t.d.*/]); - val parenPair = funE.regionFor.keywordPairs("(",")").head; - parenPair.key.append[noSpace]; - parenPair.value.prepend[noSpace]; - funE.body.format; - } - - - - def dispatch format(FunctionOrFieldAccessor fDecl, extension IFormattableDocument document) { - fDecl.configureAnnotations(document); - fDecl.configureModifiers(document); - - // State-keeper to avoid clashing reconfigurations if multiple auto-wraps get triggered. - val state = new StateTrack; // use state to only trigger one change, even if called multiple times. - - // Callback to introduce an additional line in body-block. - val (ITextSegment , IHiddenRegionFormatting , IFormattableDocument )=>void cbInsertEmptyLineInBody = [ - if(state.shouldDoThenDone){ - fDecl.body?.statements?.head.prepend[ setNewLines(2,2,maxConsecutiveNewLines); highPriority]; - } - ]; - - // Formal parameters - switch( fDecl) { - FunctionDefinition : - fDecl.fpars.configureFormalParameters(document, cbInsertEmptyLineInBody ) - N4SetterDeclaration: - fDecl.fpar.prepend[noSpace].append[noSpace] /* no autowrap for setters: cbInsertEmptyLineInBody */ - } - - // Type Variables - switch( fDecl) { - FunctionDeclaration : - fDecl.formatTypeVariables(document) - } - - // special case for accessors: get / set keywords - if(fDecl instanceof FieldAccessor){ - fDecl.configureGetSetKeyword(document); - fDecl.configureOptionality(document); - } - - - - val parenPair = fDecl.regionFor.keywordPairs("(",")").head; - parenPair.key.prepend[noSpace; newLines = 0].append[noSpace]; - parenPair.interior[indent]; - if( parenPair.isMultiLine && ! (fDecl instanceof FieldAccessor)) { - // it is already a multiline, insert the newLine immediately. - // cbInsertEmptyLineInBody.apply(null,null,null); // TODO re-think, if all will be collapsed this assumption does not hold an - } else { - // single line parameter block - } - parenPair.value.prepend[noSpace] - - for (child : fDecl.eContents) { - child.format; - } - } - - /** to be used by FunctionDefintiions and SetterDeclarations */ - def void configureFormalParameters(EList list, extension IFormattableDocument document, (ITextSegment , IHiddenRegionFormatting , IFormattableDocument )=>void x){ - if( list === null || list.isEmpty ) return; - list.forEach[it,idx| - if(idx !== 0) it.prepend[oneSpace;setNewLines(0,0,1);onAutowrap=x]; - it.append[noSpace]; - it.configureAnnotationsInLine(document); // TODO maybe we need some in-line-annotation config here. -// it.regionFor.ruleCallTo( bindingIdentifierAsFormalParameterRule ) // feature(N4JSPackage.Literals.FORMAL_PARAMETER__NAME) -// .prepend[oneSpace;newLines=0].append[] - it.declaredTypeRefInAST.format(document); - if( it.isVariadic ) - it.regionFor.keyword("...").prepend[newLines=0;/*oneSpace;*/].append[newLines=0;noSpace]; - if( it.hasInitializerAssignment ) { - it.regionFor.keyword("=").prepend[newLines=0;noSpace;].append[newLines=0;noSpace]; - if (it.initializer !== null) { - it.initializer.format(document); - } - } - ]; - } - - /** Check if key and value are in different lines. Defined for non-overlapping Regions, e.g. Keyword-pairs.*/ - def static boolean isMultiLine(Pair pair) { - ! pair.key.lineRegions.last.contains( pair.value ) - } - -// def dispatch format(FunctionOrFieldAccessor fofAccessor, extension IFormattableDocument document) { -// val begin = fofAccessor.body.semanticRegions.head -// val end = fofAccessor.body.semanticRegions.last -// if (begin?.lineRegions?.head?.contains(end?.endOffset)) { -// // same line -// } else { -// // body spans multiple lines -// begin.append[newLine;]; -// end.prepend[newLine;]; -// // fofAccessor.body.interior[indent]; // already by parenthesis? -// } -// -// fofAccessor.body?.format; -// -// } - - def dispatch void format(N4EnumDeclaration enumDecl, extension IFormattableDocument document) { - enumDecl.configureAnnotations(document); - enumDecl.configureModifiers(document); - enumDecl.insertSpaceInFrontOfCurlyBlockOpener(document); - enumDecl.indentExcludingAnnotations(document);//.interiorBUGFIX([indent],document); //enumDecl.interior[indent]; - enumDecl.configureCommas(document); - - val braces = enumDecl.regionFor.keywordPairs("{","}").head; - - val multiLine = enumDecl.isMultiline; - - enumDecl.literals.forEach[ - format; - if( multiLine ) { - if( it.regionForEObject.previousHiddenRegion.containsComment ) - { // comment above - it.prepend[newLines=2]; - } else { // no comment above - it.prepend[newLine]; - } - } - ]; - if( multiLine ) { - braces.value.prepend[newLine]; - } - } - - def dispatch void format(ParameterizedPropertyAccessExpression exp, extension IFormattableDocument document) { - val dotKW = exp.regionFor.keyword("."); - dotKW.prepend[noSpace; autowrap; setNewLines(0,0,1)].append[noSpace;]; - if( exp.eContainer instanceof ExpressionStatement) { - // top-level PPA, indent one level. - exp.interiorBUGFIX([indent],document); //exp.interior[indent]; - } - exp.target.format; - } - - def dispatch void format(ParameterizedCallExpression exp, extension IFormattableDocument document) { - // FIXME process typeArgs !!! - val dotKW = exp.regionFor.keyword("."); - dotKW.prepend[noSpace; autowrap;].append[noSpace;] - exp.regionFor.keyword("(").prepend[noSpace].append[noSpace]; - exp.regionFor.keyword(")").prepend[noSpace]; - exp.configureCommas(document); - - exp.arguments.tail.forEach[prepend[oneSpace;autowrap]]; - exp.arguments.forEach[format]; - - if( exp.eContainer instanceof ExpressionStatement) { - // top-level PPA, indent one level. - exp.interiorBUGFIX([indent],document); //exp.interior[indent]; - } - exp.target.format; - } - - - - def dispatch void format(ImportDeclaration decl, extension IFormattableDocument document) { - - // read configuration: - val extraSpace = getPreference(FORMAT_SURROUND_IMPORT_LIST_WITH_SPACE) - - decl.regionFor.keyword("{").prepend[oneSpace].append[if(extraSpace) oneSpace else noSpace]; - decl.regionFor.keyword("}").prepend[if(extraSpace) oneSpace else noSpace].append[oneSpace; newLines = 0]; - decl.regionFor.keyword("from").surround[oneSpace]; - decl.configureCommas(document); - decl.eContents.forEach[format]; - } - - def dispatch void format(NamedImportSpecifier namedImp, extension IFormattableDocument document){ - namedImp.regionFor.keyword("as").prepend[oneSpace].append[oneSpace]; - namedImp.regionFor.feature(N4JSPackage.Literals.IMPORT_SPECIFIER__DECLARED_DYNAMIC).prepend[noSpace].append[oneSpace]; // "+"-KW after alias-name - } - - def dispatch void format(NamespaceImportSpecifier nsImp, extension IFormattableDocument document){ - nsImp.regionFor.keyword("*").append[oneSpace]; - nsImp.regionFor.keyword("as").append[oneSpace]; - nsImp.regionFor.feature(N4JSPackage.Literals.IMPORT_SPECIFIER__DECLARED_DYNAMIC).prepend[noSpace].append[oneSpace]; // "+"-KW after alias-name - } - - def dispatch void format(ExportDeclaration export, extension IFormattableDocument document){ - export.regionFor.keyword("export").append[ - oneSpace; - newLines=0; - // Apply prioritization to catch cases of 'trapped' annotations e.g. "export @Final public class" which - // could also be reordered to "@Final export public class.." - priority=PRIO_13; // Priority higher then highPriority used in AnnotationList. - ]; - - export.eContents.forEach[format]; - - // Fix Trapped annotations: - val exported = export.exportedElement; - if( exported instanceof AnnotableScriptElement ){ - val annoList = exported.annotationList; - if( annoList !== null && !annoList.annotations.isEmpty) { - annoList.annotations.last.append[ - newLines = 0; oneSpace; priority = PRIO_13; - ] - } - } - } - - def dispatch void format(IfStatement stmt, extension IFormattableDocument document) { - val parenPair = stmt.regionFor.keywordPairs("(",")").head; - parenPair.interior[noSpace;indent]; - parenPair.key.prepend[oneSpace]; - parenPair.value.append[oneSpace]; - - stmt.regionFor.keyword("else").prepend[autowrap;oneSpace].append[oneSpace]; - - stmt.elseStmt.prepend[oneSpace; newLines = 0]; - - stmt.expression.format; - stmt.ifStmt.format; - stmt.elseStmt.format; - } - - def dispatch void format(SwitchStatement swStmt, extension IFormattableDocument document) { - swStmt.insertSpaceInFrontOfCurlyBlockOpener(document); - swStmt.interiorBUGFIX([indent],document); //swStmt.interior[indent]; - swStmt.expression.format; - swStmt.cases.head.prepend[newLine]; - swStmt.cases.forEach[format]; - } - - /** Formats DefaultCaseClause + CaseClause */ - def dispatch void format(AbstractCaseClause caseClause, extension IFormattableDocument document) { - caseClause.interiorBUGFIX([indent],document); //caseClause.interior[indent]; - - if (caseClause.statements.size == 1) { - if (caseClause.statements.head instanceof Block) { - caseClause.statements.head.prepend[setNewLines(0,0,0)]; - } else { - caseClause.statements.head.prepend[setNewLines(0,1,1)]; - } - } else { - caseClause.statements.head.prepend[setNewLines(1,1,1)]; - } - - // caseClause.regionFor.keyword(":").prepend[oneSpace]; // In case one space before the colon is desired - caseClause.statements.forEach[format]; - caseClause.statements.forEach[append[setNewLines(1,1,maxConsecutiveNewLines)]]; - caseClause.append[setNewLines(1, 1, maxConsecutiveNewLines)]; - } - - def dispatch void format(CastExpression expr, extension IFormattableDocument document) { - expr.regionFor.keyword("as").prepend[newLines = 0; oneSpace].append[newLines = 0; oneSpace]; - expr.expression.format; - expr.targetTypeRefNode.format; - } - - def dispatch void format(Block block, extension IFormattableDocument document) { - if( debug ) println("Formatting block "+containmentStructure(block)); - - // Beware there are blocks in the grammar, that are not surrounded by curly braces. (e.g. FunctionExpression) - - // Block not nested in other blocks usually are bodies. We want them separated by a space: - if (! (block.eContainer instanceof Block || block.eContainer instanceof Script)) { // TODO maybe invert the control here, since the block is formatting the outside. - block.regionFor.keyword("{").prepend[oneSpace]; - } - - block.interiorBUGFIX([indent],document); //block.interior[indent]; - - block.statements.head.prepend[setNewLines(1,1,maxConsecutiveNewLines)]; - block.statements.forEach[append[setNewLines(1,1,maxConsecutiveNewLines)]]; - - block.statements.forEach[format]; - - // Format empty curly blocks, necessary for comments inside: - val braces = block.regionFor.keywordPairs("{","}").head - if( braces !== null - && braces.key.nextSemanticRegion == braces.value - ) { - // empty block: - if( braces.key.nextHiddenRegion.containsComment ) { - braces.key.append[setNewLines(1,1,maxConsecutiveNewLines)]; - } else { - braces.key.append[newLines=0;noSpace]; - } - } - } - - - def dispatch void format(ReturnStatement ret, extension IFormattableDocument document) { - ret.interiorBUGFIX([indent],document); //ret.interior[indent;] - ret.expression.prepend[oneSpace; newLines = 0]; - ret.expression.format; - } - - def dispatch void format(AdditiveExpression add, extension IFormattableDocument document) { - add.regionFor.feature(N4JSPackage.Literals.ADDITIVE_EXPRESSION__OP).surround[oneSpace].append[autowrap]; - add.lhs.format - add.rhs.format - } - - def dispatch void format(MultiplicativeExpression mul, extension IFormattableDocument document) { - mul.regionFor.feature(N4JSPackage.Literals.MULTIPLICATIVE_EXPRESSION__OP).surround[oneSpace].append[autowrap]; - mul.lhs.format - mul.rhs.format - } - - def dispatch void format(BinaryBitwiseExpression binbit, extension IFormattableDocument document) { - binbit.regionFor.feature(N4JSPackage.Literals.BINARY_BITWISE_EXPRESSION__OP).surround[oneSpace]; - binbit.lhs.format - binbit.rhs.format - } - - def dispatch void format(BinaryLogicalExpression binLog, extension IFormattableDocument document) { - val opReg = binLog.regionFor.feature(N4JSPackage.Literals.BINARY_LOGICAL_EXPRESSION__OP); - opReg.surround[oneSpace]; - binLog.lhs.format - binLog.rhs.format - // auto-wrap: - val autoWrapInFront = getPreference(FORMAT_AUTO_WRAP_IN_FRONT_OF_LOGICAL_OPERATOR); - if( autoWrapInFront ) { - opReg.prepend[autowrap; lowPriority; setNewLines(0,0,1);] - } else { - opReg.append[autowrap; lowPriority; setNewLines(0,0,1);] - }; - } - - def dispatch void format(EqualityExpression eqExpr, extension IFormattableDocument document) { - eqExpr.regionFor.feature(N4JSPackage.Literals.EQUALITY_EXPRESSION__OP).surround[oneSpace].append[autowrap]; - eqExpr.lhs.format - eqExpr.rhs.format - } - - def dispatch void format(RelationalExpression relExpr, extension IFormattableDocument document) { - relExpr.regionFor.feature(N4JSPackage.Literals.RELATIONAL_EXPRESSION__OP).surround[oneSpace].append[autowrap]; - relExpr.lhs.format - relExpr.rhs.format - } - - def dispatch void format(ShiftExpression shiftExpr, extension IFormattableDocument document) { - shiftExpr.regionFor.feature(N4JSPackage.Literals.SHIFT_EXPRESSION__OP).surround[oneSpace].append[autowrap]; - shiftExpr.lhs.format - shiftExpr.rhs.format - } - - def dispatch void format(CommaExpression comma, extension IFormattableDocument document) { - comma.configureCommas(document); - comma.eContents.forEach[format]; - } - - def dispatch void format(ConditionalExpression cond, extension IFormattableDocument document) { - cond.regionFor.keyword("?").surround[oneSpace].append[autowrap; lowPriority; setNewLines(0,0,1);]; - cond.regionFor.keyword(":").surround[oneSpace].append[autowrap; lowPriority; setNewLines(0,0,1);]; - cond.expression.format; - cond.trueExpression.format; - cond.falseExpression.format; - } - - def dispatch void format(AwaitExpression await, extension IFormattableDocument document) { - await.regionFor.keyword("await").prepend[oneSpace].append[oneSpace; newLines = 0]; - await.expression.format - } - - def dispatch void format(PromisifyExpression promify, extension IFormattableDocument document) { - promify.noSpaceAfterAT(document); - promify.regionFor.keyword("Promisify").append[oneSpace]; - promify.expression.format - } - - def dispatch void format(IndexedAccessExpression idxAcc, extension IFormattableDocument document) { - val indexRegion = idxAcc.index.regionForEObject(); - indexRegion.previousSemanticRegion.prepend[noSpace;newLines=0].append[noSpace;newLines = 0]; - indexRegion.nextSemanticRegion.prepend[noSpace]; - - idxAcc.index.format; - idxAcc.target.format; - } - - def dispatch void format(NewExpression newExp, extension IFormattableDocument document) { - newExp.regionFor.keyword("new").prepend[oneSpace].append[oneSpace;newLines=0]; - newExp.callee.format; - // Watch out, commas are used in Type-args and in argument list ! If necessary distinguish by offset. - val commas = newExp.regionFor.keyword(","); - commas.prepend[noSpace].append[oneSpace]; - - // TODO maybe factor out TypeArgs formatting. - val typeArgsAngle = newExp.regionFor.keywordPairs("<",">").head; - if( typeArgsAngle !== null ) { - typeArgsAngle.key.append[noSpace;newLines=0].prepend[noSpace;newLines=0]; - typeArgsAngle.value.prepend[noSpace;newLines=0]; - } - newExp.typeArgs.forEach[format]; - - - if( newExp.isWithArgs ) { - val argParen = newExp.regionFor.keywordPairs("(",")").head; - argParen.key.prepend[newLines=0;noSpace].append[noSpace]; - argParen.value.prepend[noSpace]; - newExp.arguments.forEach[format]; - } - } - - def dispatch void format(PostfixExpression postFix, extension IFormattableDocument document) { - // no line break allowed between Expression and operator ! - postFix.regionFor.feature(N4JSPackage.Literals.POSTFIX_EXPRESSION__OP) - .prepend[newLines=0;noSpace;].append[oneSpace;lowPriority]; // giving low priority for situations of closing parenthesis: "(a++)" - postFix.expression.format; - } - - def dispatch void format(TaggedTemplateString taggedTemplate, extension IFormattableDocument document) { - taggedTemplate.regionFor.feature(N4JSPackage.Literals.EXPRESSION_WITH_TARGET__TARGET).append[ newLines = 0; oneSpace ]; - taggedTemplate.target.format; - taggedTemplate.template.format; - } - - def dispatch void format(UnaryExpression unaryExpr, extension IFormattableDocument document) { - // The operators 'void' 'delete' and 'typeof' must be separated from operand. - val boolean requireSpace=(unaryExpr.op.ordinal <= UnaryOperator.TYPEOF_VALUE); - unaryExpr.regionFor.feature(N4JSPackage.Literals.UNARY_EXPRESSION__OP) - .append[if(requireSpace) oneSpace else noSpace; newLines = 0;]; - unaryExpr.expression.format; - } - - def dispatch void format(YieldExpression yieldExpr, extension IFormattableDocument document) { - // " yield " or " yield* " - yieldExpr.regionFor.keyword("yield") - .prepend[oneSpace;] - .append[if( yieldExpr.isMany ) noSpace else oneSpace]; - if( yieldExpr.isMany ){ - yieldExpr.regionFor.keyword("*").prepend[noSpace;newLines=0].append[oneSpace] - } - yieldExpr.expression.format; - } - - def dispatch void format(ParenExpression parenE, extension IFormattableDocument document) { - parenE.semanticRegions.head.append[noSpace;newLines=0;autowrap]; - parenE.semanticRegions.last.prepend[noSpace;newLines=0;autowrap]; - parenE.interiorBUGFIX([indent],document); //parenE.interior[indent]; - parenE.expression.format; - } - - def dispatch void format(ArrowFunction arrowF, extension IFormattableDocument document) { - arrowF.configureCommas(document); - arrowF.regionFor.ruleCallTo(arrowRule).surround[oneSpace]; - arrowF.regionFor.keywordPairs("(",")").head?.interior[noSpace]; - // too lax: arrowF.fpars.configureFormalParameters(document,[/*NTD*/]); - - if( arrowF.isHasBracesAroundBody ) { - // format body as block. NOTE: this block differs from other blocks, since the curly braces are defined in the ArrowExpression. - // special handling of indentation in inside the braces. - val bracesPair = arrowF.regionFor.keywordPairs("{","}").head; - bracesPair.interior[indent]; - if( bracesPair.key.lineRegions.last.contains( bracesPair.value) // one line '{ do; stuff; }' - || bracesPair.key.lineRegions.last.contains( bracesPair.key.nextSemanticRegion ) // no line-break after braces e.g. '{ do; \n stuff; }' - ) { - // one line - arrowF.body?.statements.forEach[ it,idx| - format; if( idx !==0 ) {it.prepend[oneSpace; autowrap; newLines=0;]} - ]; - bracesPair.key.append[oneSpace]; // do not autowrap after "{" to keep wrap-semantic - bracesPair.value.prepend[oneSpace]; - } else { - // multi-line - if( arrowF.body !== null && !arrowF.body.statements.empty ) { - arrowF.body?.statements.head.prepend[newLines=1;]; - arrowF.body?.statements.forEach[format;append[newLines=1]]; - } else { - // empty block, squash interior. - bracesPair.key.append[noSpace;newLines=1;]; - bracesPair.value.prepend[noSpace;newLines=1]; - } - } - } else { - // no braces Around the implicit return statement. - arrowF.body?.statements.head.format; - } - } - - - def dispatch void format(ArrayLiteral al, extension IFormattableDocument document) { - val bracketPair = al.regionFor.keywordPairs("[","]").head; - bracketPair.interior[indent]; - val sameLine = bracketPair.key.lineRegions.head.contains( bracketPair.value ); - // auto wrap in front of AL-Elements, to preserve comma at end. - if( ! sameLine) { - al.elements.last.append[autowrap]; - al.elements.forEach[it,num|prepend[autowrap;setNewLines(0,0,1);if(num!==0)oneSpace;].append[noSpace]]; - // format last bracket if in single.line. - if( ! bracketPair.value.previousSemanticRegion.lineRegions.last.contains( bracketPair.value ) ) { - bracketPair.value.prepend[newLine]; - } - } else { - al.elements.forEach[it,num|prepend[autowrap;if(num!==0)oneSpace;]]; - } - } - - def dispatch void format(ObjectLiteral ol, extension IFormattableDocument document) { - ol.configureCommas(document); - - val bracePair = ol.regionFor.keywordPairs("{","}").head; - bracePair.interior[indent]; - - - // Decide on multiline or not. - // Rule: if opening brace is preceded by a line break, then go multiline. - val sameLine = bracePair.key.lineRegions.head.contains( bracePair.key.nextSemanticRegion.lineRegions.head ); - // OLD: val sameLine = bracePair.key.lineRegions.head.contains( bracePair.value ); - if( ! sameLine) { - bracePair.value.prepend[newLine]; // format WS in front of closing brace - ol.propertyAssignments.forEach[it,num|prepend[newLine]]; - if( bracePair.key.nextSemanticRegion == bracePair.value ) { - // empty multiline, trigger formatting: - bracePair.key.append[newLine]; - } - } else { // in one line - bracePair.key.append[newLines=0]; - ol.propertyAssignments.forEach[it,num|prepend[newLines=0;if(num!==0) { autowrap; oneSpace; } else {noSpace;}]]; - bracePair.value.prepend[newLines=0; noSpace; lowPriority]; // low priority to avoid conflict with dangling commas - } - - ol.eContents.forEach[format]; - } - - def dispatch void format( ForStatement fst, extension IFormattableDocument document){ - - fst.regionFor.keyword("for").append[oneSpace;newLines=0; autowrap]; - - val parenPair = fst.regionFor.keywordPairs("(",")").head; - parenPair.key.append[noSpace;autowrap;newLines=0]; - parenPair.value.prepend[noSpace;newLines=0].append[oneSpace;newLines=0;autowrap;]; - - fst.regionFor.keywords("in","of").forEach[ it.surround[oneSpace; newLines=0; autowrap] ]; - fst.regionFor.keywords(";").forEach[it.prepend[noSpace;newLines=0;].append[oneSpace;newLines=0;autowrap]]; - - fst.eContents.forEach[format]; - } - - def dispatch void format(TemplateLiteral tl, extension IFormattableDocument document) { - tl.interiorBUGFIX([indent],document); //tl.interior[indent;]; - tl.segments.forEach[ - switch(it) { - TemplateSegment: noOp - default: it.surround[oneSpace; autowrap;] - }; - it.format; - ]; - } - private def noOp (){} - - def dispatch void format(TemplateSegment tl, extension IFormattableDocument document) { - // just leave as is. - } - - def dispatch void format(N4TypeVariable tv, extension IFormattableDocument document) { - // "out" - if( tv.declaredCovariant ) { tv.regionFor.feature(N4JSPackage.Literals.N4_TYPE_VARIABLE__DECLARED_COVARIANT).append[oneSpace]; } - // "in" - if( tv.declaredContravariant ) {tv.regionFor.feature(N4JSPackage.Literals.N4_TYPE_VARIABLE__DECLARED_CONTRAVARIANT).append[oneSpace];} - - val upperBoundNode = tv.declaredUpperBoundNode; - if( upperBoundNode!==null ) { - // "extends" - tv.regionFor.keyword("extends").surround[oneSpace]; - upperBoundNode.immediatelyFollowing.keyword("&").surround[oneSpace]; - upperBoundNode.format(document); - } - - } - - - - def dispatch void format(Expression exp, extension IFormattableDocument document) { - switch(exp) { - // Things not to format: - BooleanLiteral, - IdentifierRef, - IntLiteral, - NullLiteral, - NumberLiteral, - RegularExpressionLiteral, - StringLiteral, - ThisLiteral, - SuperLiteral, - JSXElement - : return - } - throw new UnsupportedOperationException("expression "+exp.class.simpleName+" not yet implemented."); - } - - /** simply formats all content */ - def void genericFormat(Expression exp, extension IFormattableDocument document) { - exp.eContents.forEach[format]; - } - - - def dispatch void format(AssignmentExpression ass, extension IFormattableDocument document) { - ass.lhs.append[oneSpace] - ass.rhs.prepend[oneSpace] - ass.lhs.format; - ass.rhs.format; - } - - def dispatch void format( ExpressionStatement eStmt, extension IFormattableDocument docuemt){ - eStmt.expression.format; - } - - /** var,let,const */ - def dispatch void format(VariableStatement vStmt, extension IFormattableDocument document) { - - vStmt.configureModifiers(document); - - vStmt.regionFor.feature( - N4JSPackage.Literals.VARIABLE_DECLARATION_CONTAINER__VAR_STMT_KEYWORD).append [ - oneSpace; - ]; // "let", "var" or "const" - - vStmt.configureCommas(document); - - vStmt.interiorBUGFIX([indent],document); //vStmt.interior[indent]; - val lastIdx = vStmt.varDeclsOrBindings.size - 1; - - vStmt.varDeclsOrBindings.forEach [ e, int i | - e.format; - if (i > 0) { // assignments start in separate lines. - if (e instanceof VariableDeclaration) { - if (e.expression !== null) e.prepend[newLine] - else e.prepend[setNewLines(0,1,1); lowPriority]; - } else if (e instanceof VariableBinding) { - if (e.expression !== null) e.prepend[newLine] - else e.prepend[setNewLines(0,1,1); lowPriority]; - } - } - if (i < lastIdx) { // assignments start let following continue in separate lines. - if (e instanceof VariableDeclaration) { - if (e.expression !== null) e.immediatelyFollowing.keyword(",").append[newLine] - else e.prepend[setNewLines(0,1,1); lowPriority]; - } else if (e instanceof VariableBinding) { - if (e.expression !== null) e.immediatelyFollowing.keyword(",").append[newLine] - else e.prepend[setNewLines(0,1,1); lowPriority]; - } - - } - ]; - } - - def dispatch void format(VariableDeclaration vDecl, extension IFormattableDocument document) { - vDecl.previousHiddenRegion.set[oneSpace]; - vDecl.regionFor.keyword("=").surround[oneSpace]; - vDecl.expression.format; - vDecl.declaredTypeRefInAST.format; - } - - def dispatch void format(VariableBinding vBind, extension IFormattableDocument document) { - vBind.previousHiddenRegion.set[oneSpace]; - vBind.regionFor.keyword("=").surround[oneSpace]; - vBind.pattern.format; - vBind.expression.format; - vBind.pattern.format; - } - - def dispatch void format( BindingPattern bp, extension IFormattableDocument document) { - // ObjectBindingPattern - // ArrayBindingPattern - - // '{' or '[' - bp.semanticRegions.head.append[noSpace;newLines=0;autowrap]; - bp.semanticRegions.last.prepend[noSpace;newLines=0;autowrap]; - bp.configureCommas(document); // doesn't handle elision. - - bp.eContents.forEach[format]; - } - - - def dispatch void format(ThrowStatement thrStmt, extension IFormattableDocument document) { - thrStmt.expression.prepend[setNewLines(0, 0, 0); oneSpace]; // No autowrap, otherwise ASI - thrStmt.expression.format; - } - - - def dispatch void format(CatchBlock ctch, extension IFormattableDocument document) { - ctch.prepend[setNewLines(0, 0, 0); oneSpace]; - ctch.catchVariable.format; - ctch.block.format; - } - - def dispatch void format(FinallyBlock finlly, extension IFormattableDocument document) { - finlly.previousHiddenRegion.set[newLines = 0; oneSpace]; - finlly.block.format; - } - - /** Insert one space in front of first '{' in the direct content of the element. - * semEObject is a semanticObject, e.g. N4EnumDecl, N4Classifier ...*/ - private def void insertSpaceInFrontOfCurlyBlockOpener(EObject semEObject, extension IFormattableDocument document) { - semEObject.regionFor.keyword("{").prepend[oneSpace]; - } - - /** force: " @" and no newLine after '@' */ - private def void noSpaceAfterAT(EObject semEObject, extension IFormattableDocument document) { - semEObject.regionFor.keyword("@").append[noSpace;newLines=0].prepend[oneSpace]; - } - - /** On the direct level of an semantic Object enforce commas to ", " with autoWrap option. */ - private def void configureCommas(EObject semEObject, extension IFormattableDocument document) { - semEObject.regionFor.keywords(",").forEach [ - prepend[noSpace]; - append[oneSpace; autowrap]; - ]; - } - - def void indentExcludingAnnotations(EObject semObject, extension IFormattableDocument document) { - //Exclude Annotations from indentation field.interior[indent]; - val begin = semObject.semanticRegions.findFirst[!(semanticElement instanceof Annotation)]; - val end = semObject.semanticRegions.last; - if( begin !== end ) { // guard to prevent wrong indentation - interior(begin,end,[indent]); - } - } - - - private def dispatch void configureAnnotations(AnnotableN4MemberDeclaration semEObject, extension IFormattableDocument document) { - configureAnnotations( semEObject.annotationList, document ); - } - - private def dispatch void configureAnnotations(AnnotablePropertyAssignment semEObject, extension IFormattableDocument document) { - configureAnnotations( semEObject.annotationList, document ); - } - - private def dispatch void configureAnnotations(AnnotableScriptElement semEObject, extension IFormattableDocument document) { - configureAnnotations( semEObject.annotationList, document ); - } - - private def dispatch void configureAnnotations(AnnotableExpression semEObject, extension IFormattableDocument document) { - configureAnnotations( semEObject.annotationList, document ); - } - - private def dispatch void configureAnnotations(AbstractAnnotationList aList, extension IFormattableDocument document) { - if( aList === null || aList.annotations.isEmpty ) return; - - aList.prepend[setNewLines(2,2,2);highPriority]; // TODO in case of trapped in Annotation like 'export @Final public class A{}' - a reorder would be necessary (see format for export) - aList.append[newLine]; // TODO special annotations like @Internal ? --> together with public, reorder to be in same line? - aList.annotations.forEach[it, idx | - it.configureAnnotation(document,true,idx ===0); - ]; - } - - /** - * - * @param withLineWraps true do line-wrapping - * @param isFirstenAnnotation if this is the first annotation in a sequence ( used with line-wrapping in front of '@') - */ - private def configureAnnotation(Annotation it, extension IFormattableDocument document, boolean withLineWraps, boolean isFirstAnnotation ){ - // configure arguments - val parens = it.regionFor.keywordPairs("(",")").head; - if( parens !== null ) { - parens=>[ - it.key.prepend[noSpace].append[noSpace]; - it.value.prepend[noSpace].append[if( withLineWraps ) {noSpace; newLines=1;} else {oneSpace; newLines=0;}]; - it.interior[indent]; - // line break before "@": - if( withLineWraps && !isFirstAnnotation ) { - it.key.previousSemanticRegion.previousSemanticRegion.prepend[newLines = 1]; - } - ]; - it.configureCommas(document); - } - - // Configure @-Syntax - // Special case here: for "@XY" we can either get "@" or "XY" as the first semantic element - it.semanticRegions.head =>[ - if( it.grammarElement instanceof Keyword) { - // assume '@' - it.append[ noSpace; newLines=0 ]; - } else { - it.prepend[ // for "@Final" "Final" will be the first semantic region in case of exported classes, - noSpace; newLines=0 - ]; - } - ]; - } - - private def dispatch void configureAnnotations(Object semEObject, extension IFormattableDocument document) { - // no annotations to be configured. - } - - private def dispatch void configureAnnotations(Void x, extension IFormattableDocument document) { - // no annotations to be configured. - } - - private def void configureAnnotationsInLine(FormalParameter fpar, extension IFormattableDocument document) { - if( fpar.annotations.isEmpty ) return; - // (@x @y("") bogus a:typ) - fpar.annotations.head=>[ - it.configureAnnotation(document,false,true); - prepend[noSpace;newLines=0;autowrap;]; - ] - fpar.annotations.tail.forEach[ - configureAnnotation(document,false,false); - prepend[oneSpace;newLines=0;autowrap;]; - ] - fpar.annotations.last.append[oneSpace;newLines=0;autowrap;]; - } - - - /** only script-level annotations '@@' */ - private def void formatScriptAnnotations(Script script, extension IFormattableDocument document) { - if( script.annotations.isEmpty ) return; - - if (script.annotations.head.previousHiddenRegion.containsComment) { - script.annotations.head.prepend[noSpace;]; - } else { - script.annotations.head.prepend[noSpace;newLines=0;]; - } - script.annotations.last.append[setNewLines(2,2,2)]; - - script.annotations.forEach[it,idx| - if( idx !==0 ) it.prepend[newLines=1;noSpace]; - it.semanticRegions.head=>[ // its an '@@' - append[noSpace;newLines=0] - ] - ] - - } - - - - - public override ITextReplacer createCommentReplacer(IComment comment) { - // Overridden to distinguish between JSDOC-style, standard ML, formatter-off ML-comment. - val EObject grammarElement = comment.getGrammarElement(); - if (grammarElement instanceof AbstractRule) { - val String ruleName = (/*(AbstractRule)*/ grammarElement).getName(); - if (ruleName.startsWith("ML")) { - val cText = comment.text; - if (cText.startsWith("/**") && !cText.startsWith("/***")) { // JSDOC - return new N4MultilineCommentReplacer(comment, '*'); - } else if (cText.startsWith("/*-")) { // Turn-off formatting. - return new OffMultilineCommentReplacer(comment, !comment.isNotFirstInLine); - } else { // All other - return new FixedMultilineCommentReplacer(comment); - } - } - if (ruleName.startsWith("SL")) { - if (comment.isNotFirstInLine) { - return new SinglelineDocCommentReplacer(comment, "//"); - } else { - return new SinglelineCodeCommentReplacer(comment, "//"); - } - } - } - - // fall back to super-impl. - super.createCommentReplacer(comment); - } - - private static def boolean isNotFirstInLine(IComment comment) { - val lineRegion = comment.getLineRegions().get(0); - - return !comment.contains( lineRegion.offset ); - } - - - public override createTextReplacerMerger(){ - return new IndentHandlingTextReplaceMerger(this); - } - - /** DEBUG-helper */ - private def static String containmentStructure(EObject eo) { - val name = eo.class.simpleName; - if( eo.eContainer !== null ) - return '''«eo.eContainer.containmentStructure».«eo.eContainingFeature.name»-> «name»''' - return name - } - - /** HELPER to avoid Warnings in code, since @SuppressWarnings("unused") is not active in xtend code.*/ - def suppressUnusedWarnings(Object ... e) - { - PRIO_4; - } - - /** - * Simple tracker that only gives exactly one time the value {@code true} - * when calling {@link StateTrack#shouldDoThenDone()} - */ - private final static class StateTrack { - private boolean done=false; - - /** - * This method returns {@code true} exactly on it's first invocation. Proceeding calls always return {@code false}. - * - * @return Returns {@code true} if not done, immediately switches {@link #done} to {@code true}; - * returns {@code false} if already done. - */ - def boolean shouldDoThenDone(){ - val ret = !done; - done = true; - return ret; - } - } - - - /**************************************************************************************************************** - * - * Type Expression - * - ***************************************************************************************************************/ - def dispatch void format(UnionTypeExpression ute, extension IFormattableDocument document) { - ute.regionFor.keywords("|").forEach[ surround[oneSpace;newLines=0].prepend[autowrap;highPriority] ]; - ute.typeRefs.forEach[format]; - // OLD syntax: - val kwUnion = ute.regionFor.keyword("union"); - if( kwUnion !== null ) { - kwUnion.prepend[oneSpace].append[oneSpace;newLines=0].nextSemanticRegion/*'{'*/.append[oneSpace;newLines=0]; - ute.semanticRegions.last/*'}'*/.prepend[oneSpace;newLines=0]; - } - } - - def dispatch void format(IntersectionTypeExpression ite, extension IFormattableDocument document) { - ite.regionFor.keywords("&").forEach[surround[oneSpace;newLines=0].prepend[autowrap;highPriority]]; - ite.typeRefs.forEach[format]; - // OLD syntax - val kwInersection = ite.regionFor.keyword("intersection"); - if( kwInersection !== null ) { - kwInersection.prepend[oneSpace].append[oneSpace;newLines=0].nextSemanticRegion/*'{'*/.append[oneSpace;newLines=0]; - ite.semanticRegions.last/*'}'*/.prepend[oneSpace;newLines=0]; - } - } - - def dispatch void format( TStructMember tsm, extension IFormattableDocument document) { - if(tsm instanceof TField) { - tsm.configureOptionality(document); - } else if(tsm instanceof org.eclipse.n4js.ts.types.FieldAccessor) { - tsm.configureGetSetKeyword(document); - tsm.configureOptionality(document); - - val parenPair = tsm.regionFor.keywordPairs("(",")").head; - parenPair.key.prepend[noSpace; newLines = 0].append[noSpace]; - } - // get, set, method, field - tsm.eContents.forEach[format;]; - // TODO format TStruct* more thoroughly - // (note: added some TStructMember formatting while working on IDE-2405, but it is still incomplete!) - } - - - - def private void configureUndefModifier( StaticBaseTypeRef sbtr, extension IFormattableDocument document){ - // UndefModifier "?" - sbtr.regionFor.feature(TypeRefsPackage.Literals.TYPE_REF__FOLLOWED_BY_QUESTION_MARK).prepend[noSpace;newLines=0;]; - } - - def dispatch void format( ThisTypeRef ttr, extension IFormattableDocument document) { - ttr.configureUndefModifier(document); - if( ttr instanceof ThisTypeRefStructural) { - ttr.interiorBUGFIX([indent],document) - configureStructuralAdditions(ttr,document); - ttr.eContents.forEach[ - format; - ] - } - } - - def dispatch void format( ParameterizedTypeRef ptr, extension IFormattableDocument document) { - ptr.interiorBUGFIX([indent],document); //ptr.interior[indent]; - ptr.configureUndefModifier(document); - - // Union / Intersection - ptr.regionFor.keywords("&","|").forEach[ surround[oneSpace;newLines=0].append[autowrap;highPriority]]; - // Short-Hand Syntax for Arrays - if( ptr.isArrayTypeExpression ) { - ptr.regionFor.keyword("[").append[noSpace]; - ptr.regionFor.keyword("]").append[noSpace]; - } - // Short-Hand Syntax for IterableN - if( ptr.isArrayNTypeExpression ) { - ptr.regionFor.keyword("[").append[noSpace]; - ptr.regionFor.keyword("]").append[noSpace]; - } - ptr.formatTypeArguments(document); - - // ParameterizedTypeRefStructural : - configureStructuralAdditions(ptr,document); - - // generically format content: - ptr.eContents.forEach[format] - } - - /** used for "~X with {}" except for the 'X' part. */ - def void configureStructuralAdditions( TypeRef ptr, extension IFormattableDocument document) { - val semRegTypingStrategy = ptr.regionFor.ruleCallTo(typingStrategyUseSiteOperatorRule); - if( semRegTypingStrategy!== null) { - semRegTypingStrategy.prepend[oneSpace].append[noSpace;newLines=0;]; - - // declaredType - semRegTypingStrategy.nextSemanticRegion.append[]; - - val kwWith = ptr.regionFor.keyword("with"); - if( kwWith !== null ) { - kwWith.surround[oneSpace;newLines=0;autowrap]; - val bracesPair = ptr.regionFor.keywordPairs("{","}").head; - bracesPair.key.append[noSpace;newLines=0;autowrap]; - bracesPair.value.prepend[noSpace;newLines=0;autowrap]; - //ptr.regionFor.keywords(",",";").forEach[ prepend[noSpace;newLines=0].append[oneSpace;newLines=0;autowrap] ] - ptr.regionFor.keywords(";").forEach[ prepend[noSpace;newLines=0].append[oneSpace;newLines=0;autowrap;lowPriority] ] - ((ptr as StructuralTypeRef).astStructuralMembers.tail - .forEach[ regionForEObject.previousHiddenRegion.set[oneSpace;newLines=0;autowrap]] - ); - } - } - } - - /** formats type argument section including outside border. */ - def void formatTypeArguments(ParameterizedTypeRef semObject, extension IFormattableDocument document) { - if( semObject.declaredTypeArgs.isEmpty ) return; - // to "<": - semObject.regionFor.keyword("<").append[noSpace].prepend[noSpace; newLines=0; lowPriority]; - semObject.regionFor.keyword(">").prepend[noSpace].append[noSpace; newLines=0; lowPriority]; - for( typeArg: semObject.declaredTypeArgs ){ - typeArg.append[noSpace].immediatelyFollowing.keyword(",").append[oneSpace]; - typeArg.format(document); - } - - } - - - - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Configure Methods - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - def private void configureGetSetKeyword(FieldAccessor fieldAccessor, extension IFormattableDocument document) { - val kw = if(fieldAccessor instanceof GetterDeclaration) "get" else "set"; - fieldAccessor.regionFor.keyword(kw).prepend[oneSpace].append[oneSpace; newLines=0; autowrap]; - } - def private void configureGetSetKeyword(org.eclipse.n4js.ts.types.FieldAccessor tFieldAccessor, extension IFormattableDocument document) { - val kw = if(tFieldAccessor instanceof TGetter) "get" else "set"; - tFieldAccessor.regionFor.keyword(kw).prepend[oneSpace].append[oneSpace; newLines=0; autowrap]; - } - - def private void configureOptionality(N4FieldDeclaration fieldDecl, extension IFormattableDocument document) { - fieldDecl.regionFor.feature(N4JSPackage.Literals.N4_FIELD_DECLARATION__DECLARED_OPTIONAL).prepend[noSpace;newLines=0;]; - } - def private void configureOptionality(FieldAccessor fieldAccessor, extension IFormattableDocument document) { - fieldAccessor.regionFor.feature(N4JSPackage.Literals.FIELD_ACCESSOR__DECLARED_OPTIONAL).prepend[noSpace;newLines=0;]; - } - def private void configureOptionality(TField tField, extension IFormattableDocument document) { - tField.regionFor.feature(TypesPackage.Literals.TFIELD__OPTIONAL).prepend[noSpace;newLines=0;]; - } - def private void configureOptionality(org.eclipse.n4js.ts.types.FieldAccessor tFieldAccessor, extension IFormattableDocument document) { - tFieldAccessor.regionFor.feature(TypesPackage.Literals.FIELD_ACCESSOR__OPTIONAL).prepend[noSpace;newLines=0;]; - } - - - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Bug-workarounds - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /** Temporarily used method to replace document.interior(EObject, Procedure1) to prevent wrong indentations. - * - * Main pattern replace document-extension method call: - *
-	 * object.interior[indent]
-	 * 
- * by - *
-	 * object.interiorBUGFIX([indent],document);
-	 * 
-	 *
-	 */
-	def void interiorBUGFIX(EObject object, Procedure1 init,IFormattableDocument document ){
-		val IEObjectRegion objRegion = getTextRegionAccess().regionForEObject(object);
-		if (objRegion !== null) {
-			val IHiddenRegion previous = objRegion.getPreviousHiddenRegion();
-			val IHiddenRegion next = objRegion.getNextHiddenRegion();
-			if (previous !== null && next !== null && previous != next) {
-				val nsr = previous.getNextSemanticRegion();
-				val psr = next.getPreviousSemanticRegion();
-				if( nsr != psr )
-				{ // standard case
-					document.interior(nsr, psr , init);
-				} else {
-					// former error-case:
-					// there is no interior --> don't do anything!
-					//
-					// applying to the next HiddenRegion is a bad idea,
-					// since it could wrongly indent a multiline-comment (c.f. GHOLD-260)
-				}
-			}
-		}
-	}
-
-
-	/** Dummy method to prevent accidentally calling interior - extension method form document. You should call interiorBUGFIX instead ! */
-	def Procedure1 interior( EObject eo, Procedure1 init ){
-		throw new IllegalStateException("Method should not be called.")
-	}
-
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatterPreferenceKeys.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatterPreferenceKeys.java
new file mode 100644
index 0000000000..ac9d39dff7
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatterPreferenceKeys.java
@@ -0,0 +1,42 @@
+/**
+ * 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.formatting2;
+
+import org.eclipse.xtext.formatting2.FormatterPreferenceKeys;
+import org.eclipse.xtext.preferences.BooleanKey;
+import org.eclipse.xtext.preferences.IntegerKey;
+
+/**
+ *
+ */
+public class N4JSFormatterPreferenceKeys extends FormatterPreferenceKeys {
+	/***/
+	public static BooleanKey FORMAT_PARENTHESIS = new BooleanKey("format.parenthesis", false);
+	/***/
+	public static BooleanKey FORMAT_SURROUND_PAREN_CONTENT_WITH_SPACE = new BooleanKey(
+			"format.surround_paren_content_with_space", false);
+	/***/
+	public static IntegerKey FORMAT_MAX_CONSECUTIVE_NEWLINES = new IntegerKey("format.max_consecutive_newlines", 2);
+	/***/
+	public static BooleanKey FORMAT_SWITCH_CASES_HAVE_SPACE_IN_FRONT_OF_COLON = new BooleanKey(
+			"format.switch_cases_have_space_in_front_of_colon", false);
+	/***/
+	public static BooleanKey FORMAT_AUTO_WRAP_IN_FRONT_OF_LOGICAL_OPERATOR = new BooleanKey(
+			"format.auto_wrap_in_front_of_logical_operator", true);
+	/**
+	 * Considering the code import a, {b,c,d} from "xy"; a value of true will render an
+	 * additional space after "{" and one before the closing bracket "}" Default value is false and the
+	 * line will be rendered as above.
+	 */
+	public static BooleanKey FORMAT_SURROUND_IMPORT_LIST_WITH_SPACE = new BooleanKey(
+			"format.surround_import_list_with_space", false);
+
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatterPreferenceKeys.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatterPreferenceKeys.xtend
deleted file mode 100644
index c14a9a65f7..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSFormatterPreferenceKeys.xtend
+++ /dev/null
@@ -1,31 +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.formatting2
-
-import org.eclipse.xtext.formatting2.FormatterPreferenceKeys
-import org.eclipse.xtext.preferences.BooleanKey
-import org.eclipse.xtext.preferences.IntegerKey
-
-/**
- *
- */
-class N4JSFormatterPreferenceKeys extends FormatterPreferenceKeys {
-	public static val BooleanKey FORMAT_PARENTHESIS = new BooleanKey("format.parenthesis", false);
-	public static val BooleanKey FORMAT_SURROUND_PAREN_CONTENT_WITH_SPACE = new BooleanKey("format.surround_paren_content_with_space", false);
-	public static val IntegerKey FORMAT_MAX_CONSECUTIVE_NEWLINES = new IntegerKey("format.max_consecutive_newlines",2);
-	public static val BooleanKey FORMAT_SWITCH_CASES_HAVE_SPACE_IN_FRONT_OF_COLON = new BooleanKey("format.switch_cases_have_space_in_front_of_colon", false);
-	public static val BooleanKey FORMAT_AUTO_WRAP_IN_FRONT_OF_LOGICAL_OPERATOR= new BooleanKey("format.auto_wrap_in_front_of_logical_operator", true);
-	/** Considering the code import a, {b,c,d} from "xy"; a value of true will render an additional space after "{" and one before the closing bracket "}"
-	 * Default value is false and the line will be rendered as above.*/
-	public static val BooleanKey FORMAT_SURROUND_IMPORT_LIST_WITH_SPACE = new BooleanKey("format.surround_import_list_with_space", false);
-
-
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.java
new file mode 100644
index 0000000000..17560c88f9
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.java
@@ -0,0 +1,368 @@
+/**
+ * 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.formatting2;
+
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.groupBy;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.last;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.map;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.sortBy;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.log4j.Logger;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.n4js.services.N4JSGrammarAccess;
+import org.eclipse.xtend.lib.annotations.Accessors;
+import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor;
+import org.eclipse.xtext.RuleCall;
+import org.eclipse.xtext.formatting2.AbstractFormatter2;
+import org.eclipse.xtext.formatting2.IFormattableDocument;
+import org.eclipse.xtext.formatting2.IHiddenRegionFormatting;
+import org.eclipse.xtext.formatting2.ITextReplacer;
+import org.eclipse.xtext.formatting2.ITextReplacerContext;
+import org.eclipse.xtext.formatting2.internal.HiddenRegionReplacer;
+import org.eclipse.xtext.formatting2.internal.TextReplacerMerger;
+import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
+import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
+import org.eclipse.xtext.formatting2.regionaccess.ITextRegionExtensions;
+import org.eclipse.xtext.formatting2.regionaccess.ITextSegment;
+import org.eclipse.xtext.xbase.lib.Pair;
+
+/**
+ *
+ */
+@SuppressWarnings("restriction")
+@FinalFieldsConstructor
+class N4JSGenericFormatter {
+
+	N4JSGrammarAccess grammarAccess;
+	ITextRegionExtensions trExtensions;
+
+	public static int PRIO_1 = -10;
+	public static int PRIO_2 = -9;
+	public static int PRIO_3 = -8;
+
+	public N4JSGenericFormatter(N4JSGrammarAccess grammarAccess, ITextRegionExtensions trExtensions) {
+		this.grammarAccess = grammarAccess;
+		this.trExtensions = trExtensions;
+	}
+
+	public void formatColon(EObject semanticElement, IFormattableDocument document) {
+		for (ISemanticRegion colon : trExtensions.allRegionsFor(semanticElement).keywords(":")) {
+			ISemanticRegion sr = document.prepend(colon, hrf -> {
+				hrf.noSpace();
+				hrf.setNewLines(0);
+				hrf.setPriority(PRIO_3);
+			});
+			document.append(sr, hrf -> {
+				hrf.oneSpace();
+				hrf.setPriority(PRIO_2);
+			});
+		}
+	}
+
+	/**
+	 * Formats whitespace around already present semicolons (;) and inserts new semicolons where the parser expects
+	 * them.
+	 */
+	public void formatSemicolons(EObject script, IFormattableDocument document) {
+		for (ISemanticRegion region : trExtensions.allRegionsFor(script).ruleCallsTo(grammarAccess.getSemiRule())) {
+			String text = region.getText();
+			IHiddenRegion previous = region.getPreviousHiddenRegion();
+			if (text == ";") {
+
+				// there is already a ";" so let's format it
+				document.prepend(region, hrf -> {
+					hrf.noSpace();
+					hrf.setNewLines(0);
+					hrf.highPriority();
+				});
+			} else if (region.getNextSemanticRegion() != null && "}".equals(region.getNextSemanticRegion().getText())
+					&& !region.isMultiline()) {
+				// do nothing
+			} else if (previous.containsComment()) {
+
+				// we're behind a comment - insert semicolon before the comment
+				ITextSegment insertAt = region.getTextRegionAccess().regionForOffset(previous.getOffset(), 0);
+				document.addReplacer(new InsertSemi(insertAt, ";"));
+			} else if (text.trim().isEmpty()) {
+				// Don't eat up white space here.
+				// Look for first line break and replace with ";\n":
+				int lbIdx = text.indexOf("\n");
+				if (lbIdx >= 0) {
+					ITextSegment replaceRegion = region.getTextRegionAccess().regionForOffset(region.getOffset(),
+							lbIdx + 1);
+					document.addReplacer(new InsertSemi(replaceRegion, ";\n"));
+				} else {
+					// the text region only contains whitespace, e.g. so let's insert a ; and a "\n"
+					document.addReplacer(new InsertSemi(region, ";"));
+				}
+			} else {
+
+				// we probably are the comment, so let's prefix it with ;
+				ITextSegment insertAt = region.getTextRegionAccess().regionForOffset(region.getOffset(), 0);
+				document.addReplacer(new InsertSemi(insertAt, ";"));
+			}
+		}
+	}
+
+	/**
+	 * Format whitespace around (), [], and {}
+	 *
+	 * When multiple pairs of (), [], or {} open in the same line, indentation is only applied for the innermost pair.
+	 */
+	public void formatParenthesisBracketsAndBraces(EObject script, IFormattableDocument document) {
+		List> all = new ArrayList<>();
+		all.addAll(trExtensions.allRegionsFor(script).keywordPairs("(", ")"));
+		all.addAll(trExtensions.allRegionsFor(script).keywordPairs("{", "}"));
+		all.addAll(trExtensions.allRegionsFor(script).keywordPairs("[", "]"));
+
+		Map>> byLine = groupBy(all,
+				p -> p.getKey().getLineRegions().get(0).getOffset());
+
+		for (Entry>> e : byLine.entrySet()) {
+			List> bracePairsInSameLine = sortBy(e.getValue(),
+					p -> p.getKey().getOffset());
+			Pair outermost = bracePairsInSameLine.get(0);
+			Pair innermost = last(bracePairsInSameLine);
+
+			// keep track of lastOpen/lastClose to avoid formatting the HiddenRegion twice if that hidden region
+			// is located directly between two brackets. Then, first.append[] would collide with second.prepend[].
+			IHiddenRegion lastOpen = null;
+			IHiddenRegion lastClose = null;
+			for (Pair pair : bracePairsInSameLine) {
+				ISemanticRegion open = pair.getKey();
+				ISemanticRegion close = pair.getValue();
+				if (open.getPreviousHiddenRegion() != lastOpen && lastOpen != null) {
+					// something between last opening and this one; null-check for first opening-> don't force noSpace
+					// here
+					document.prepend(open, hrf -> {
+						hrf.noSpace();
+						hrf.setPriority(PRIO_1);
+					});
+				}
+				if (pair != innermost) {
+					document.append(open, hrf -> {
+						hrf.noSpace();
+						hrf.setPriority(PRIO_1);
+					});
+					document.prepend(close, hrf -> {
+						hrf.noSpace();
+						hrf.setPriority(PRIO_1);
+					});
+				}
+				if (close.getNextHiddenRegion() != lastClose) {
+					if (pair == outermost) {
+						// close.appendNewLine(document)
+					} else {
+						document.append(close, hrf -> {
+							hrf.noSpace();
+							hrf.setPriority(PRIO_1);
+						});
+					}
+				}
+				lastOpen = open.getNextHiddenRegion();
+				lastClose = close.getPreviousHiddenRegion();
+			}
+
+			ISemanticRegion open = innermost.getKey();
+			ISemanticRegion close = innermost.getValue();
+			if (open.getNextSemanticRegion() == close && !open.getNextHiddenRegion().isMultiline()) { // empty
+																										// brace-pair
+				document.append(open, hrf -> {
+					hrf.noSpace();
+					hrf.setPriority(PRIO_1);
+				});
+			} // otherwise, if there is a newline before the innermost closing bracket, we want to format the surrounded
+				// tokens multiline style.
+			else if (close.getPreviousHiddenRegion().isMultiline()) {
+				appendNewLine(document.prepend(close, hrf -> {
+					hrf.newLine();
+					hrf.setPriority(PRIO_1);
+				}), document);
+				document.append(open, hrf -> {
+					hrf.newLine();
+					hrf.setPriority(PRIO_1);
+				});
+				document.interior(innermost, hrf -> hrf.indent());
+				for (ISemanticRegion comma : trExtensions.regionFor(open.getSemanticElement()).keywords(",")) {
+					// FIXME: bug in toString: println(comma.toString);
+					ISemanticRegion sr = document.prepend(comma, hrf -> {
+						hrf.noSpace();
+						hrf.setPriority(PRIO_1);
+					});
+					document.append(sr, hfr -> {
+						// If dangling comma, then a conflict arises with new line of close.
+						// Avoid here by using Prio_2
+						hfr.setNewLines(1, 1, 2);
+						hfr.setPriority(PRIO_2);
+					});
+				}
+			} // otherwise, format the tokens into a single line.
+			else {
+				if (document.getRequest().getPreferences()
+						.getPreference(N4JSFormatterPreferenceKeys.FORMAT_SURROUND_PAREN_CONTENT_WITH_SPACE)) {
+					// configured way to have single space in parenthesised expression.
+					document.prepend(close, hrf -> {
+						hrf.oneSpace();
+						hrf.setPriority(PRIO_1);
+					});
+					document.append(open, hrf -> {
+						hrf.oneSpace();
+						hrf.setPriority(PRIO_1);
+					});
+				}
+				for (ISemanticRegion comma : trExtensions.regionFor(open.getSemanticElement()).keywords(",")) {
+					ISemanticRegion sr = document.prepend(comma, hrf -> {
+						hrf.noSpace();
+						hrf.setPriority(PRIO_1);
+					});
+					document.append(sr, hrf -> {
+						hrf.oneSpace();
+						hrf.setPriority(PRIO_1);
+					});
+				}
+			}
+		}
+	}
+
+	public ISemanticRegion appendNewLine(ISemanticRegion appendAfter, IFormattableDocument doc) {
+		EObject semi = appendAfter.getNextSemanticRegion() == null ? null
+				: appendAfter.getNextSemanticRegion().getGrammarElement();
+		if (semi instanceof RuleCall && ((RuleCall) semi).getRule() == grammarAccess.getSemiRule()) {
+			// noop, handled by org.eclipse.n4js.formatting2.N4JSGenericFormatter.formatSemicolons(EObject,
+			// IFormattableDocument)
+		} else {
+			doc.append(appendAfter, hrf -> {
+				hrf.newLine();
+				hrf.setPriority(PRIO_1);
+			});
+		}
+		return appendAfter;
+	}
+
+	static interface InsertSemiBase extends ITextReplacer {
+		// marker interface
+	}
+
+	@Accessors
+	static class InsertSemi implements InsertSemiBase {
+		ITextSegment region;
+		String text;
+
+		public InsertSemi(ITextSegment region, String text) {
+			this.region = region;
+			this.text = text;
+		}
+
+		@Override
+		public ITextReplacerContext createReplacements(ITextReplacerContext context) {
+			context.addReplacement(region.replaceWith(text));
+			return context;
+		}
+
+		@Override
+		public ITextSegment getRegion() {
+			return this.region;
+		}
+
+		public String getText() {
+			return this.text;
+		}
+	}
+
+	static class InsertSemiFollowedByTextReplacer implements InsertSemiBase {
+		InsertSemiBase insertSemi;
+		ITextReplacer textReplacer;
+		ITextSegment region;
+
+		InsertSemiFollowedByTextReplacer(InsertSemiBase insertSemi, ITextReplacer textReplacer) {
+			this.insertSemi = insertSemi;
+			this.textReplacer = textReplacer;
+			this.region = insertSemi.getRegion().merge(textReplacer.getRegion());
+		}
+
+		@Override
+		public ITextReplacerContext createReplacements(ITextReplacerContext context) {
+			// First insert semicolon
+			ITextReplacerContext replContext = insertSemi.createReplacements(context).withReplacer(textReplacer);
+			// Then apply the text replacer
+			return textReplacer.createReplacements(replContext);
+		}
+
+		@Override
+		public ITextSegment getRegion() {
+			return this.region;
+		}
+	}
+
+	static class IndentHandlingTextReplaceMerger extends TextReplacerMerger {
+		private final static Logger LOGGER = Logger.getLogger(IndentHandlingTextReplaceMerger.class);
+
+		AbstractFormatter2 fmt;
+
+		IndentHandlingTextReplaceMerger(AbstractFormatter2 formatter) {
+			super(formatter);
+			fmt = formatter;
+		}
+
+		/**
+		 * Overridden for special case of {@link InsertSemi} & {@link HiddenRegionReplacer} merging. Calls super
+		 * implementation if no InsertSemi object is involved
+		 */
+		@Override
+		public ITextReplacer merge(List conflicting) {
+			if (findFirst(conflicting, tr -> tr instanceof InsertSemiBase) == null) {
+				// standard case, but not handled as we want by super because due to ASI there can be
+				// HiddenRegionReplacer that have equal offsets and length but are not identical.
+				List hrf = toList(filter(conflicting, HiddenRegionReplacer.class));
+				if (hrf.size() == conflicting.size()) {
+					IHiddenRegionFormatting merged = fmt.createHiddenRegionFormattingMerger()
+							.merge(toList(map(hrf, hrr -> hrr.getFormatting())));
+					if (merged != null) {
+						return fmt.createHiddenRegionReplacer(hrf.get(0).getRegion(), merged);
+					}
+				}
+				return super.merge(conflicting);
+			}
+
+			// there is an insertSemi.
+			List semiReplacements = toList(
+					filter(conflicting, tr -> tr instanceof InsertSemiBase));
+			List otherReplacements = toList(
+					filter(conflicting, tr -> !(tr instanceof InsertSemiBase)));
+
+			if (semiReplacements.size() != 1 || otherReplacements.size() != 1) {
+				LOGGER.warn("""
+						Unhandled merge-case: "
+							"Semis replacer («semiReplacements.size») :«semiReplacements»
+							"Non-Semi replacer ( «otherReplacements.size»  «otherReplacements»
+						""");
+				return null; // null creates merge Exception
+			}
+			// exactly one:
+			InsertSemiBase semiRepl = (InsertSemiBase) semiReplacements.get(0);
+			ITextReplacer otherRepl = otherReplacements.get(0);
+
+			if (otherRepl instanceof HiddenRegionReplacer) {
+				return new InsertSemiFollowedByTextReplacer(semiRepl, otherRepl);
+			}
+
+			return null;
+		}
+	}
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.xtend
deleted file mode 100644
index 886173e018..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/N4JSGenericFormatter.xtend
+++ /dev/null
@@ -1,257 +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.formatting2
-
-import org.eclipse.n4js.services.N4JSGrammarAccess
-import org.eclipse.emf.ecore.EObject
-import org.eclipse.xtend.lib.annotations.Accessors
-import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor
-import org.eclipse.xtext.RuleCall
-import org.eclipse.xtext.formatting2.IFormattableDocument
-import org.eclipse.xtext.formatting2.ITextReplacer
-import org.eclipse.xtext.formatting2.ITextReplacerContext
-import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion
-import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion
-import org.eclipse.xtext.formatting2.regionaccess.ITextRegionExtensions
-import org.eclipse.xtext.formatting2.regionaccess.ITextSegment
-import org.eclipse.xtext.formatting2.internal.TextReplacerMerger
-import org.eclipse.xtext.formatting2.AbstractFormatter2
-import java.util.List
-import org.eclipse.n4js.utils.Log
-import org.eclipse.xtext.formatting2.internal.HiddenRegionReplacer
-
-/**
- *
- */
-@FinalFieldsConstructor class N4JSGenericFormatter {
-
-	val extension N4JSGrammarAccess
-	val extension ITextRegionExtensions
-
-	public static val PRIO_1 = -10
-	public static val PRIO_2 = -9
-	public static val PRIO_3 = -8
-
-	def void formatColon(EObject semanticElement, extension IFormattableDocument document) {
-		for (colon : semanticElement.allRegionsFor.keywords(":")) {
-			colon.prepend[noSpace; newLines = 0; priority = PRIO_3]
-				 .append[oneSpace; priority = PRIO_2];
-		}
-	}
-
-	/**
-	 * Formats whitespace around already present semicolons (;) and inserts new semicolons where the parser expects them.
-	 */
-	def void formatSemicolons(EObject script, extension IFormattableDocument document) {
-		for (region : script.allRegionsFor.ruleCallsTo(semiRule)) {
-			val text = region.text;
-			val previous = region.previousHiddenRegion;
-			if (text == ";") {
-
-				// there is already a ";" so let's format it
-				region.prepend[noSpace; newLines = 0; highPriority];
-			} else if(region.nextSemanticRegion?.text == "}" && !region.isMultiline){
-				// do nothing
-			}
-			else if (previous.containsComment) {
-
-				// we're behind a comment - insert semicolon before the comment
-				val insertAt = region.textRegionAccess.regionForOffset(previous.offset, 0)
-				document.addReplacer(new InsertSemi(insertAt, ";"));
-			} else if (text.trim.isEmpty) {
-				// Don't eat up white space here.
-				// Look for first line break and replace with ";\n":
-				val lbIdx = text.indexOf("\n");
-				if( lbIdx >= 0 ) {
-					val replaceRegion = region.textRegionAccess.regionForOffset(region.offset, lbIdx+1 );
-					document.addReplacer(new InsertSemi(replaceRegion, ";\n"));
-				} else {
-					// the text region only contains whitespace, e.g. so let's insert a ; and a "\n"
-					document.addReplacer(new InsertSemi(region, ";"));
-				}
-			} else {
-
-				// we probably are the comment, so let's prefix it with ;
-				val insertAt = region.textRegionAccess.regionForOffset(region.offset, 0);
-				document.addReplacer(new InsertSemi(insertAt, ";"));
-			}
-		}
-	}
-
-	/**
-	 * Format whitespace around (), [], and {}
-	 *
-	 * When multiple pairs of (), [], or {} open in the same line, indentation is only applied for the innermost pair.
-	 */
-	def void formatParenthesisBracketsAndBraces(EObject script, extension IFormattableDocument document) {
-		val all = newArrayList()
-		all += script.allRegionsFor.keywordPairs("(", ")")
-		all += script.allRegionsFor.keywordPairs("{", "}")
-		all += script.allRegionsFor.keywordPairs("[", "]")
-
-		val byLine = all.groupBy[key.lineRegions.head.offset]
-
-		for (e : byLine.entrySet) {
-			val bracePairsInSameLine = e.value.sortBy[key.offset]
-			val outermost = bracePairsInSameLine.head
-			val innermost = bracePairsInSameLine.last
-
-			// keep track of lastOpen/lastClose to avoid formatting the HiddenRegion twice if that hidden region
-			// is located directly between two brackets. Then, first.append[] would collide with second.prepend[].
-			var IHiddenRegion lastOpen = null
-			var IHiddenRegion lastClose = null
-			for (pair : bracePairsInSameLine) {
-				val open = pair.key
-				val close = pair.value
-				if (open.previousHiddenRegion != lastOpen && lastOpen !== null) { // something between last opening and this one; null-check for first opening-> don't force noSpace here
-					open.prepend[noSpace; priority = PRIO_1]
-				}
-				if (pair !== innermost) {
-					open.append[noSpace; priority = PRIO_1]
-					close.prepend[noSpace; priority = PRIO_1]
-				}
-				if (close.nextHiddenRegion != lastClose) {
-					if (pair === outermost) {
-//						close.appendNewLine(document)
-					} else {
-						close.append[noSpace; priority = PRIO_1]
-					}
-				}
-				lastOpen = open.nextHiddenRegion
-				lastClose = close.previousHiddenRegion
-			}
-
-			val ISemanticRegion open = innermost.key;
-			val ISemanticRegion close = innermost.value;
-			if (open.nextSemanticRegion == close && !open.nextHiddenRegion.isMultiline) { // empty brace-pair
-				open.append[noSpace; priority = PRIO_1];
-			} // otherwise, if there is a newline before the innermost closing bracket, we want to format the surrounded tokens multiline style.
-			else if (close.previousHiddenRegion.isMultiline) {
-				close.prepend[newLine; priority = PRIO_1].appendNewLine(document);
-				open.append[newLine; priority = PRIO_1];
-				innermost.interior[indent];
-				for (comma : open.semanticElement.regionFor.keywords(",")) {
-					// FIXME: bug in toString: println(comma.toString);
-					comma.prepend[noSpace; priority = PRIO_1]
-					.append[
-						// If dangling comma, then a conflict arises with new line of close.
-						// Avoid here by using Prio_2
-						setNewLines(1,1,2);
-						priority = PRIO_2
-					];
-				}
-			} // otherwise, format the tokens into a single line.
-			else {
-				if( document.request.preferences.getPreference( N4JSFormatterPreferenceKeys.FORMAT_SURROUND_PAREN_CONTENT_WITH_SPACE ) ) { // configured way to have single space in parenthesised expression.
-					close.prepend[oneSpace; priority = PRIO_1]
-					open.append[oneSpace; priority = PRIO_1]
-				}
-				for (comma : open.semanticElement.regionFor.keywords(",")) {
-					comma.prepend[noSpace; priority = PRIO_1].append[oneSpace; priority = PRIO_1]
-				}
-			}
-		}
-	}
-
-	def ISemanticRegion appendNewLine(ISemanticRegion appendAfter, extension IFormattableDocument doc) {
-		val semi = appendAfter.nextSemanticRegion?.grammarElement
-		if (semi instanceof RuleCall && (semi as RuleCall)?.rule == semiRule) {
-			// noop, handled by org.eclipse.n4js.formatting2.N4JSGenericFormatter.formatSemicolons(EObject, IFormattableDocument)
-		} else {
-			appendAfter.append[newLine; priority = PRIO_1]
-		}
-		return appendAfter
-	}
-}
-
-interface InsertSemiBase extends ITextReplacer {}
-
-@Accessors class InsertSemi implements InsertSemiBase {
-	val ITextSegment region
-	val String text
-
-	override createReplacements(ITextReplacerContext context) {
-		context.addReplacement(region.replaceWith(text))
-		return context;
-	}
-}
-
-class InsertSemiFollowedByTextReplacer implements InsertSemiBase {
-	val InsertSemiBase insertSemi;
-	val ITextReplacer textReplacer;
-	val ITextSegment region;
-	
-	new(InsertSemiBase insertSemi, ITextReplacer textReplacer) {
-		this.insertSemi = insertSemi;
-		this.textReplacer = textReplacer;
-		this.region = insertSemi.region.merge(textReplacer.region)
-	}
-
-	override createReplacements(ITextReplacerContext context) {
-		// First insert semicolon
-		var replContext = insertSemi.createReplacements(context).withReplacer(textReplacer);
-		// Then apply the text replacer
-		return textReplacer.createReplacements(replContext);
-	}
-	
-	override getRegion() {
-		return this.region;
-	}
-}
-
-@Log
-class IndentHandlingTextReplaceMerger extends TextReplacerMerger {
-	val AbstractFormatter2 fmt
-
-	new(AbstractFormatter2 formatter) {
-		super(formatter)
-		fmt = formatter
-	}
-
-	/** Overridden for special case of {@link InsertSemi} & {@link HiddenRegionReplacer} merging.
-	 * Calls super implementation if no InsertSemi object is involved */
-	override merge(List conflicting) {
-		if(conflicting.findFirst[it instanceof InsertSemiBase] === null ) {
- 			// standard case, but not handled as we want by super because due to ASI there can be
- 			// HiddenRegionReplacer that have equal offsets and length but are not identical.
- 			val hrf = conflicting.filter(HiddenRegionReplacer).toList
- 			if(hrf.size === conflicting.size) {
- 				val merged = fmt.createHiddenRegionFormattingMerger.merge(hrf.map[formatting])
- 				if(merged !== null) {
- 					return fmt.createHiddenRegionReplacer(hrf.head.region, merged)
- 				}
- 			}
-			return super.merge(conflicting);
-		}
-
-		// there is an insertSemi.
-		val semiReplacements = conflicting.filter[it instanceof InsertSemiBase].toList;
-		val otherReplacements = conflicting.filter[!(it instanceof InsertSemiBase)].toList;
-
-		if( semiReplacements.size !== 1  || otherReplacements.size !== 1  ) {
-			logger.warn( '''
-			Unhandled merge-case: "
-				"Semis replacer («semiReplacements.size») :«semiReplacements»
-				"Non-Semi replacer ( «otherReplacements.size»  «otherReplacements»
-			 ''');
-			return null; // null creates merge Exception
-		}
-		// exactly one:
-		val semiRepl = semiReplacements.get(0) as InsertSemiBase;
-		val otherRepl = otherReplacements.get(0);
-		
-		if( otherRepl instanceof HiddenRegionReplacer ) {
-			return new InsertSemiFollowedByTextReplacer(semiRepl, otherRepl);
-		}
-
-		return null;
-	}
-}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionsFormatter.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionFormatterNoOp.java
similarity index 61%
rename from plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionsFormatter.xtend
rename to plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionFormatterNoOp.java
index 5f3645ec9a..896e4444e5 100644
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionsFormatter.xtend
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionFormatterNoOp.java
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2016 NumberFour AG.
+ * Copyright (c) 2024 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
@@ -10,16 +10,15 @@
  */
 package org.eclipse.n4js.formatting2;
 
-import org.eclipse.xtext.formatting2.AbstractFormatter2
-import org.eclipse.xtext.formatting2.IFormattableDocument
-
-abstract class TypeExpressionsFormatter extends AbstractFormatter2 {
-}
+import org.eclipse.xtext.formatting2.IFormattableDocument;
 
+/**
+ *
+ */
 public class TypeExpressionFormatterNoOp extends TypeExpressionsFormatter {
 
-	override format(Object obj, IFormattableDocument document) {
-		throw new UnsupportedOperationException("TypeExpressionFormatter should not be used directly.")
+	@Override
+	public void format(Object obj, IFormattableDocument document) {
+		throw new UnsupportedOperationException("TypeExpressionFormatter should not be used directly.");
 	}
-
 }
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDescriptionUtils.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionsFormatter.java
similarity index 55%
rename from plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDescriptionUtils.xtend
rename to plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionsFormatter.java
index 7509a2c621..9ff8ad432f 100644
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDescriptionUtils.xtend
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/formatting2/TypeExpressionsFormatter.java
@@ -8,16 +8,10 @@
  * Contributors:
  *   NumberFour AG - Initial API and implementation
  */
-package org.eclipse.n4js.resource
+package org.eclipse.n4js.formatting2;
 
-import org.eclipse.xtext.resource.DescriptionUtils
-import org.eclipse.xtext.resource.IResourceDescription
+import org.eclipse.xtext.formatting2.AbstractFormatter2;
 
-/**
- */
-class N4JSDescriptionUtils extends DescriptionUtils {
-
-	override collectOutgoingReferences(IResourceDescription description) {
-		newHashSet
-	}
+abstract class TypeExpressionsFormatter extends AbstractFormatter2 {
+	// empty
 }
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/AbstractSubGenerator.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/AbstractSubGenerator.java
new file mode 100644
index 0000000000..c09948e05e
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/AbstractSubGenerator.java
@@ -0,0 +1,539 @@
+/**
+ * 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.generator;
+
+import static org.eclipse.xtext.diagnostics.Severity.ERROR;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.head;
+import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList;
+
+import java.io.StringWriter;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.eclipse.emf.common.EMFPlugin;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.n4js.N4JSGlobals;
+import org.eclipse.n4js.N4JSLanguageConstants;
+import org.eclipse.n4js.generator.IGeneratorMarkerSupport.Severity;
+import org.eclipse.n4js.n4JS.Script;
+import org.eclipse.n4js.packagejson.projectDescription.ProjectType;
+import org.eclipse.n4js.resource.N4JSCache;
+import org.eclipse.n4js.resource.N4JSResource;
+import org.eclipse.n4js.ts.types.TModule;
+import org.eclipse.n4js.utils.FolderContainmentHelper;
+import org.eclipse.n4js.utils.ResourceNameComputer;
+import org.eclipse.n4js.utils.ResourceType;
+import org.eclipse.n4js.utils.StaticPolyfillHelper;
+import org.eclipse.n4js.utils.URIUtils;
+import org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot;
+import org.eclipse.n4js.workspace.N4JSSourceFolderSnapshot;
+import org.eclipse.n4js.workspace.N4JSWorkspaceConfigSnapshot;
+import org.eclipse.n4js.workspace.WorkspaceAccess;
+import org.eclipse.xtext.generator.AbstractFileSystemAccess;
+import org.eclipse.xtext.generator.IFileSystemAccess;
+import org.eclipse.xtext.generator.IFileSystemAccess2;
+import org.eclipse.xtext.generator.IGenerator2;
+import org.eclipse.xtext.generator.IGeneratorContext;
+import org.eclipse.xtext.generator.OutputConfiguration;
+import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
+import org.eclipse.xtext.service.OperationCanceledManager;
+import org.eclipse.xtext.util.CancelIndicator;
+import org.eclipse.xtext.util.UriExtensions;
+import org.eclipse.xtext.validation.CheckMode;
+import org.eclipse.xtext.validation.IResourceValidator;
+import org.eclipse.xtext.validation.Issue;
+
+import com.google.inject.Inject;
+
+/**
+ * All sub generators should extend this class. It provides basic blocks of the logic, and shared implementations.
+ */
+abstract public class AbstractSubGenerator implements ISubGenerator, IGenerator2 {
+	private final static Logger LOGGER = Logger.getLogger(AbstractSubGenerator.class);
+
+	private CompilerDescriptor compilerDescriptor = null;
+
+	/***/
+	@Inject
+	protected StaticPolyfillHelper staticPolyfillHelper;
+
+	/***/
+	@Inject
+	protected WorkspaceAccess workspaceAccess;
+
+	/***/
+	@Inject
+	protected ResourceNameComputer resourceNameComputer;
+
+	/***/
+	@Inject
+	protected IResourceValidator resVal;
+
+	/***/
+	@Inject
+	protected N4JSCache cache;
+
+	/***/
+	@Inject
+	protected IGeneratorMarkerSupport genMarkerSupport;
+
+	/***/
+	@Inject
+	protected OperationCanceledManager operationCanceledManager;
+
+	/***/
+	@Inject
+	protected GeneratorExceptionHandler exceptionHandler;
+
+	/***/
+	@Inject
+	protected N4JSPreferenceAccess preferenceAccess;
+
+	@Inject
+	private FolderContainmentHelper containmentHelper;
+
+	@Inject
+	private UriExtensions uriExtensions;
+
+	@Override
+	public CompilerDescriptor getCompilerDescriptor() {
+		if (compilerDescriptor == null) {
+			compilerDescriptor = getDefaultDescriptor();
+		}
+		return compilerDescriptor;
+	}
+
+	@Override
+	public void setCompilerDescriptor(CompilerDescriptor compilerDescriptor) {
+		this.compilerDescriptor = compilerDescriptor;
+	}
+
+	@Override
+	public void doGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {
+		doGenerate(input, fsa);
+	}
+
+	@Override
+	public void beforeGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {
+		// TODO Auto-generated method stub
+
+	}
+
+	@Override
+	public void afterGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {
+		// TODO Auto-generated method stub
+
+	}
+
+	/**
+	 * This override declares an {@link IFileSystemAccess} parameter. At runtime, its actual type depends on whether IDE
+	 * or headless, which in turn determines whether the actual argument has a progress monitor or not:
+	 * 
    + *
  • IDE scenario: Actual type is {@code EclipseResourceFileSystemAccess2}. A progress monitor can be obtained via + * {@code getMonitor()}. It is checked automatically behind the scenes, for example in {@code generateFile()}. Upon + * detecting a pending cancellation request, an {@code OperationCanceledException} is raised.
  • + *
  • Headless scenario: Actual type is {@code JavaIoFileSystemAccess}. No progress monitor is available.
  • + *
+ */ + @Override + public void doGenerate(Resource input, IFileSystemAccess fsa) { + if (justCopy(input)) { + copyWithoutTranspilation(input, fsa); + return; + } + + N4JSWorkspaceConfigSnapshot ws = workspaceAccess.getWorkspaceConfig(input); + + try { + // remove error-marker + genMarkerSupport.deleteMarker(input); + + updateOutputPath(ws, fsa, getCompilerID(), input); + internalDoGenerate(ws, input, GeneratorOption.DEFAULT_OPTIONS, fsa); + + } catch (UnresolvedProxyInSubGeneratorException e) { + genMarkerSupport.createMarker(input, e.getMessage(), Severity.ERROR); + + } catch (Exception e) { + // cancellation is not an error case, so simply propagate as usual + operationCanceledManager.propagateIfCancelException(e); + + // issue error marker + String target = input.getURI() == null ? "unknown" : input.getURI().toString(); + if (input instanceof N4JSResource && ((N4JSResource) input).getModule() != null) { + target = ((N4JSResource) input).getModule().getModuleSpecifier(); + } + String msgMarker = "Severe error occurred while transpiling module " + target + + ". Check error log for details about the failure."; + genMarkerSupport.createMarker(input, msgMarker, Severity.ERROR); + + // re-throw as GeneratorException to have the frameworks notify the error. + if (e instanceof GeneratorException) { + throw e; + } + String msg = (e.getMessage() == null) ? "type=" + e.getClass().getName() : "message=" + e.getMessage(); + exceptionHandler.handleError("Severe error occurred in transpiler=" + getCompilerID() + " " + msg + ".", e); + } + } + + @Override + public boolean shouldBeCompiled(Resource input, CancelIndicator monitor) { + N4JSWorkspaceConfigSnapshot ws = workspaceAccess.getWorkspaceConfig(input); + + boolean autobuildEnabled = isActive(input); + boolean isXPECTMode = N4JSGlobals.XT_FILE_EXTENSION + .equals(URIUtils.fileExtension(input.getURI()).toLowerCase()); + URI inputUri = input.getURI(); + + boolean result = (autobuildEnabled + && isGenerateProjectType(ws, inputUri) + && hasOutput(ws, inputUri) + && isOutputNotInSourceContainer(ws, inputUri) + && isOutsideOfOutputFolder(ws, inputUri) + && isSource(ws, inputUri) + && (isNoValidate(ws, inputUri) + || isExternal(ws, inputUri) + // if platform is running (but not in XPECT mode) the generator is called from the builder, + // hence cannot have any validation errors + // (note: XPECT swallows errors hence we cannot rely on Eclipse in case of .xt files) + || ((EMFPlugin.IS_ECLIPSE_RUNNING && !isXPECTMode) || hasNoErrors(input, monitor)))) + && (!isStaticPolyfillingModule(input)) // compile driven by filled type + && hasNoPolyfillErrors(input, monitor); + return result; + } + + private boolean hasOutput(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) { + return getOutputPath(ws, n4jsSourceURI) != null; + } + + private boolean isSource(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) { + return ws.findSourceFolderContaining(n4jsSourceURI) != null; + } + + private boolean isNoValidate(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) { + return ws.isNoValidate(n4jsSourceURI); + } + + private boolean isExternal(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) { + N4JSSourceFolderSnapshot sourceContainerOpt = ws.findSourceFolderContaining(n4jsSourceURI); + if (sourceContainerOpt != null) { + N4JSSourceFolderSnapshot sourceContainer = sourceContainerOpt; + return sourceContainer.isExternal(); + } + return false; + } + + /** + * If the resource has a static polyfill, then ensure it is error-free. Calls + * {@link #hasNoErrors(Resource, CancelIndicator)} on the static polyfill resource. + */ + private boolean hasNoPolyfillErrors(Resource input, CancelIndicator monitor) { + N4JSResource resSPoly = staticPolyfillHelper.getStaticPolyfillResource(input); + if (resSPoly == null) { + return true; + } + // re-validation is necessary since the changes of the current resource (i.e. filled resource) + // can affect the filling resource in a way that validation errors will be removed or created. + cache.recreateIssues(resVal, resSPoly, CheckMode.ALL, monitor); + return hasNoErrors(resSPoly, monitor); + } + + /** + * Does validation report no errors for the given resource? If errors exists, log them as a side-effect. If + * validation was canceled before finishing, don't assume absence of errors. + */ + private boolean hasNoErrors(Resource input, CancelIndicator monitor) { + List issues = cache.getOrUpdateIssues(resVal, input, CheckMode.ALL, monitor); + if (issues == null || input instanceof N4JSResource && !((N4JSResource) input).isFullyProcessed()) { + // Cancellation occurred likely before all validations completed, thus can't assume absence of errors. + // Cancellation may result in exit via normal control-flow (this case) or via exceptional control-flow (see + // exception handler below) + warnDueToCancelation(input, null); + return false; + } + + List errors = toList(filter(issues, i -> i.getSeverity() == ERROR)); + if (errors.isEmpty()) { + return true; + } + if (LOGGER.isDebugEnabled()) { + for (Issue it : errors) { + LOGGER.debug(input.getURI() + " " + it.getMessage() + " " + it.getSeverity() + " @L_" + + it.getLineNumber() + " "); + } + } + return false; + } + + private void warnDueToCancelation(Resource input, Throwable exc) { + String msg = "User canceled the validation of " + input.getURI() + ". Will not compile."; + if (null == exc) { + LOGGER.warn(msg); + } else { + LOGGER.warn(msg, exc); + } + } + + /** @return true iff the current project has a project type that is supposed to generate code. */ + private boolean isGenerateProjectType(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) { + N4JSProjectConfigSnapshot project = ws.findProjectContaining(n4jsSourceURI); + if (project != null) { + ProjectType projectType = project.getType(); + if (N4JSGlobals.PROJECT_TYPES_WITHOUT_GENERATION.contains(projectType)) { + return false; + } + } + + return true; + } + + /** @return true iff the given resource does not lie within the output folder. */ + private boolean isOutsideOfOutputFolder(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) { + return !containmentHelper.isContainedInOutputFolder(ws, n4jsSourceURI); + } + + /** @return true iff the output folder of the given n4js resource is not contained by a source container. */ + private boolean isOutputNotInSourceContainer(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) { + N4JSProjectConfigSnapshot project = ws.findProjectContaining(n4jsSourceURI); + if (project != null) { + return !containmentHelper.isOutputContainedInSourceContainer(ws, project); + } else { + return false; + } + } + + /** + * Actual generation to be overridden by subclasses. + */ + abstract protected void internalDoGenerate(N4JSWorkspaceConfigSnapshot ws, Resource resource, + GeneratorOption[] options, IFileSystemAccess access); + + /** + * Returns the name of the target file (without path) to which the source is to be compiled to. Default + * implementation returns a configured project Name with version + file name + extension. E.g., "proj-0.0.1/p/A.js" + * for a file A in proj. + * + * Convenience method, to provide all necessary API for the sub-classes. Delegates to + * {@link ResourceNameComputer#generateFileDescriptor(Resource, String)}. + */ + protected String getTargetFileName(Resource n4jsSourceFile, String compiledFileExtension) { + return resourceNameComputer.generateFileDescriptor(n4jsSourceFile, compiledFileExtension); + } + + /** + * Convenient access to the Script-Element + */ + protected Script rootElement(Resource resource) { + return head(filter(resource.getContents(), Script.class)); + } + + /** The file-extension of the compiled result */ + protected String getCompiledFileExtension(Resource input) { + return preferenceAccess.getPreference(input, getCompilerID(), CompilerProperties.COMPILED_FILE_EXTENSION, + getDefaultDescriptor()); + } + + /** The file-extension of the source-map to the compiled result */ + protected String getCompiledFileSourceMapExtension(Resource input) { + return preferenceAccess.getPreference(input, getCompilerID(), + CompilerProperties.COMPILED_FILE_SOURCEMAP_EXTENSION, + getDefaultDescriptor()); + } + + /** Adjust output-path of the generator to match the N4JS projects-settings. */ + private void updateOutputPath(N4JSWorkspaceConfigSnapshot ws, IFileSystemAccess fsa, String compilerID, + Resource input) { + String outputPath = getOutputPath(ws, input.getURI()); + if (outputPath == null) { + outputPath = N4JSLanguageConstants.DEFAULT_PROJECT_OUTPUT; + } + if (fsa instanceof AbstractFileSystemAccess) { + OutputConfiguration conf = ((AbstractFileSystemAccess) fsa).getOutputConfigurations().get(compilerID); + if (conf != null) { + conf.setOutputDirectory(outputPath); + } + } + } + + private static String getOutputPath(N4JSWorkspaceConfigSnapshot ws, URI nestedLocation) { + if (ws.findProjectContaining(nestedLocation) == null) { + return null; + } + if (ws.findProjectContaining(nestedLocation).getProjectDescription() == null) { + return null; + } + return ws.findProjectContaining(nestedLocation).getProjectDescription().getOutputPath(); + } + + /** Navigation from the generated output-location to the location of the input-resource */ + @SuppressWarnings("unused") + protected Path calculateNavigationFromOutputToSourcePath(N4JSWorkspaceConfigSnapshot ws, IFileSystemAccess fsa, + String compilerID, N4JSResource input) { + // --- Project locations --- + N4JSProjectConfigSnapshot project = ws.findProjectContaining(input.getURI()); + + // /home/user/workspace/Project/ + Path projectPath = project.getPathAsFileURI().toFileSystemPath(); + // platform:/resource/Project/ + URI projectLocURI = project.getPathAsFileURI().withTrailingPathDelimiter().toURI(); + + // --- output locations --- + // src-gen + String outputPath = project.getOutputPath(); + // Project/a/b/c + Path outputRelativeLocation = getOutputRelativeLocation(input); + + // --- source locations --- + // src/a/b/c + URI inputURI = uriExtensions.withEmptyAuthority(input.getURI()); + URI completetSourceURI = inputURI.trimSegments(1).deresolve(projectLocURI); + String completetSource = URIUtils.toFile(completetSourceURI).toString(); + + // Handling case when source container is the project root itself. (Sources { source { '.' } }) + if (null == completetSource && project.getPathAsFileURI().toURI() == input.getURI().trimSegments(1)) { + completetSource = projectPath.toFile().getAbsolutePath(); + } + + // /home/user/workspace/Project/src-gen/a/b/c + Path fullOutpath = projectPath.resolve(outputPath).normalize().resolve(outputRelativeLocation).normalize(); + // /home/user/workspace/Project/src/a/b/c + Path fullSourcePath = projectPath.resolve(completetSource).normalize(); + + // ../../../../../../src/a/b/c + Path rel = fullOutpath.relativize(fullSourcePath); + + return rel; + } + + /** + * Calculates local output path for a given resource. Depending on the configuration this path can be in various + * forms, {@code Project-1.0.0/a/b/c/}, {@code Project/a/b/c/} or just {@code a/b/c/} + * + */ + private Path getOutputRelativeLocation(N4JSResource input) { + URI uri = uriExtensions.withEmptyAuthority(input.getURI()); + // Project/a/b/c/Input.XX + Path localOutputFilePath = Paths.get(resourceNameComputer.generateFileDescriptor(input, uri, ".XX")); + + // if calculated path has just one element, e.g. "Input.XX" + // then local path segment is empty + if (localOutputFilePath.getNameCount() < 2) { + return Paths.get(""); + } + + // otherwise strip resource to get local path, i.e. Project/a/b/c/Input.XX => Project/a/b/c/ + return localOutputFilePath.subpath(0, localOutputFilePath.getNameCount() - 1); + } + + /** + * TODO IDE-1487 currently there is no notion of default compiler. We fake call to the ES5 sub generator. + */ + final static String calculateProjectBasedOutputDirectory(N4JSProjectConfigSnapshot project, + boolean includeProjectName) { + return (includeProjectName) ? project.getPackageName() + "/" + project.getOutputPath() + : project.getOutputPath(); + } + + /** Access to compiler ID */ + abstract public String getCompilerID(); + + /** Access to compiler descriptor */ + abstract public CompilerDescriptor getDefaultDescriptor(); + + /** Answers: Is this compiler activated for the input at hand? */ + public boolean isActive(Resource input) { + return Boolean.valueOf(preferenceAccess.getPreference(input, getCompilerID(), CompilerProperties.IS_ACTIVE, + getDefaultDescriptor())); + } + + /** + * Checking the availability of a static polyfill, which will override the compilation of this module. + **/ + public boolean isNotStaticallyPolyfilled(Resource resource) { + // val TModule tmodule = (N4JSResource::getModule(resource) ); // for some reason xtend cannot see static + // getModule ?! + if (resource instanceof N4JSResource) { + TModule tmodule = N4JSResource.getModule(resource); + // 1. resource must be StaticPolyfillAware and + // 2. there must exist a polyfilling instance (second instance with same fqn) + if (tmodule.isStaticPolyfillAware()) { + // search for second instance. + if (staticPolyfillHelper.hasStaticPolyfill(resource)) { + return false; + } + } + } + return true; + } + + /** + * Checking if this resource represents a static polyfill, which will contribute to a filled resource. + **/ + private boolean isStaticPolyfillingModule(Resource resource) { + TModule tmodule = (N4JSResource.getModule(resource)); + if (null != tmodule) { + return tmodule.isStaticPolyfillModule(); + } + return false; + } + + /** + * + * @return true if the composite generator is applicable to the given resource and false otherwise. + */ + @Override + public boolean isApplicableTo(Resource input) { + return shouldBeCompiled(input, null); + } + + /** + * Depending on the file-extension, determines if the given resource requires actual transpilation as opposed to + * simply copying the source file to the output folder. + * + * @param eResource + * N4JS resource to check. + * @return true if the code requires transpilation. + */ + protected boolean justCopy(Resource eResource) { + ResourceType resourceType = ResourceType.getResourceType(eResource); + return !(resourceType.equals(ResourceType.N4JS) || resourceType.equals(ResourceType.N4JSX) + || resourceType.equals(ResourceType.N4JSD)); + } + + /** + * Take the content of resource and copy it over to the output folder without any transformation. + * + * @param resource + * JS-code snippet which will be treated as text. + * @param fsa + * file system access + */ + protected void copyWithoutTranspilation(Resource resource, IFileSystemAccess fsa) { + StringWriter outCode = new StringWriter(); + // get script + EObject script = resource.getContents().get(0); + + // obtain text + String scriptAsText = NodeModelUtils.getNode(script).getRootNode().getText(); + + // write + String decorated = scriptAsText.toString(); + outCode.write(decorated); + String filename = resourceNameComputer.generateFileDescriptor(resource, + URIUtils.fileExtension(resource.getURI())); + fsa.generateFile(filename, getCompilerID(), outCode.toString()); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/AbstractSubGenerator.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/AbstractSubGenerator.xtend deleted file mode 100644 index 56c17a092f..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/AbstractSubGenerator.xtend +++ /dev/null @@ -1,482 +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.generator - -import com.google.inject.Inject -import java.io.StringWriter -import java.nio.file.Path -import java.nio.file.Paths -import java.util.List -import org.eclipse.emf.common.EMFPlugin -import org.eclipse.emf.common.util.URI -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.n4js.N4JSGlobals -import org.eclipse.n4js.N4JSLanguageConstants -import org.eclipse.n4js.generator.IGeneratorMarkerSupport.Severity -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.resource.N4JSCache -import org.eclipse.n4js.resource.N4JSResource -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.utils.FolderContainmentHelper -import org.eclipse.n4js.utils.Log -import org.eclipse.n4js.utils.ResourceNameComputer -import org.eclipse.n4js.utils.ResourceType -import org.eclipse.n4js.utils.StaticPolyfillHelper -import org.eclipse.n4js.utils.URIUtils -import org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot -import org.eclipse.n4js.workspace.N4JSWorkspaceConfigSnapshot -import org.eclipse.n4js.workspace.WorkspaceAccess -import org.eclipse.xtend.lib.annotations.Accessors -import org.eclipse.xtext.generator.AbstractFileSystemAccess -import org.eclipse.xtext.generator.IFileSystemAccess -import org.eclipse.xtext.generator.IFileSystemAccess2 -import org.eclipse.xtext.generator.IGenerator2 -import org.eclipse.xtext.generator.IGeneratorContext -import org.eclipse.xtext.nodemodel.util.NodeModelUtils -import org.eclipse.xtext.service.OperationCanceledManager -import org.eclipse.xtext.util.CancelIndicator -import org.eclipse.xtext.util.UriExtensions -import org.eclipse.xtext.validation.CheckMode -import org.eclipse.xtext.validation.IResourceValidator -import org.eclipse.xtext.validation.Issue - -import static org.eclipse.xtext.diagnostics.Severity.* - -/** - * All sub generators should extend this class. It provides basic blocks of the logic, and - * shared implementations. - */ -@Log -abstract class AbstractSubGenerator implements ISubGenerator, IGenerator2 { - - @Accessors - private CompilerDescriptor compilerDescriptor = null - - @Inject protected StaticPolyfillHelper staticPolyfillHelper - - @Inject protected WorkspaceAccess workspaceAccess - - @Inject protected ResourceNameComputer resourceNameComputer - - @Inject protected IResourceValidator resVal - - @Inject protected N4JSCache cache - - @Inject protected IGeneratorMarkerSupport genMarkerSupport - - @Inject protected OperationCanceledManager operationCanceledManager - - @Inject protected GeneratorExceptionHandler exceptionHandler - - @Inject protected N4JSPreferenceAccess preferenceAccess - - @Inject private FolderContainmentHelper containmentHelper; - - @Inject private UriExtensions uriExtensions; - - - override getCompilerDescriptor() { - if (compilerDescriptor === null) { - compilerDescriptor = getDefaultDescriptor - } - return compilerDescriptor - } - - - override doGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) { - doGenerate(input, fsa); - } - - override beforeGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) { - // TODO Auto-generated method stub - - } - - override afterGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) { - // TODO Auto-generated method stub - - } - - /** - * This override declares an {@link IFileSystemAccess} parameter. At runtime, its actual type depends on whether IDE or headless, - * which in turn determines whether the actual argument has a progress monitor or not: - *
    - *
  • - * IDE scenario: Actual type is {@code EclipseResourceFileSystemAccess2}. A progress monitor can be obtained via {@code getMonitor()}. - * It is checked automatically behind the scenes, for example in {@code generateFile()}. - * Upon detecting a pending cancellation request, an {@code OperationCanceledException} is raised. - *
  • - *
  • - * Headless scenario: Actual type is {@code JavaIoFileSystemAccess}. No progress monitor is available. - *
  • - *
- */ - override doGenerate(Resource input, IFileSystemAccess fsa) { - if (justCopy(input)) { - copyWithoutTranspilation(input, fsa); - return; - } - - val ws = workspaceAccess.getWorkspaceConfig(input); - - try { - // remove error-marker - genMarkerSupport.deleteMarker(input) - - updateOutputPath(ws, fsa, getCompilerID, input); - internalDoGenerate(ws, input, GeneratorOption.DEFAULT_OPTIONS, fsa); - - } catch (UnresolvedProxyInSubGeneratorException e) { - genMarkerSupport.createMarker(input, e.message, Severity.ERROR); - - } catch (Exception e) { - // cancellation is not an error case, so simply propagate as usual - operationCanceledManager.propagateIfCancelException(e); - - // issue error marker - val target = (if (input instanceof N4JSResource) input.module?.moduleSpecifier) ?: input.URI?.toString; - val msgMarker = "Severe error occurred while transpiling module " + target - + ". Check error log for details about the failure."; - genMarkerSupport.createMarker(input, msgMarker, Severity.ERROR); - - // re-throw as GeneratorException to have the frameworks notify the error. - if (e instanceof GeneratorException) { - throw e; - } - var msg = if (e.message === null) "type=" + e.class.name else "message=" + e.message; - exceptionHandler.handleError('''Severe error occurred in transpiler=«compilerID» «msg».''', e); - } - } - - override shouldBeCompiled(Resource input, CancelIndicator monitor) { - val ws = workspaceAccess.getWorkspaceConfig(input); - - val autobuildEnabled = isActive(input) - val isXPECTMode = N4JSGlobals.XT_FILE_EXTENSION.equals(URIUtils.fileExtension(input.URI).toLowerCase); - val inputUri = input.URI - - val boolean result = (autobuildEnabled - && isGenerateProjectType(ws, inputUri) - && hasOutput(ws, inputUri) - && isOutputNotInSourceContainer(ws, inputUri) - && isOutsideOfOutputFolder(ws, inputUri) - && isSource(ws, inputUri) - && (isNoValidate(ws, inputUri) - || isExternal(ws, inputUri) - // if platform is running (but not in XPECT mode) the generator is called from the builder, hence cannot have any validation errors - // (note: XPECT swallows errors hence we cannot rely on Eclipse in case of .xt files) - || ((EMFPlugin.IS_ECLIPSE_RUNNING && !isXPECTMode) || hasNoErrors(input, monitor)) - )) - && (!input.isStaticPolyfillingModule) // compile driven by filled type - && hasNoPolyfillErrors(input,monitor) - return result - } - - private def hasOutput(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI){ - return getOutputPath(ws, n4jsSourceURI) !== null; - } - private def isSource(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) { - return ws.findSourceFolderContaining(n4jsSourceURI) !== null; - } - - private def isNoValidate(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) { - return ws.isNoValidate(n4jsSourceURI); - } - - private def isExternal(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) { - val sourceContainerOpt = ws.findSourceFolderContaining(n4jsSourceURI); - if (sourceContainerOpt !== null) { - val sourceContainer = sourceContainerOpt; - return sourceContainer.external; - } - return false; - } - - /** If the resource has a static polyfill, then ensure it is error-free. - * Calls {@link #hasNoErrors()} on the static polyfill resource. - */ - private def boolean hasNoPolyfillErrors(Resource input, CancelIndicator monitor) { - val resSPoly = staticPolyfillHelper.getStaticPolyfillResource(input) - if (resSPoly === null) { - return true; - } - // re-validation is necessary since the changes of the current resource (i.e. filled resource) - // can affect the filling resource in a way that validation errors will be removed or created. - cache.recreateIssues(resVal, resSPoly, CheckMode.ALL, monitor); - return hasNoErrors(resSPoly, monitor) - } - - /** - * Does validation report no errors for the given resource? - * If errors exists, log them as a side-effect. - * If validation was canceled before finishing, don't assume absence of errors. - */ - private def boolean hasNoErrors(Resource input, CancelIndicator monitor) { - val List issues = cache.getOrUpdateIssues(resVal, input, CheckMode.ALL, monitor); - if (issues === null || input instanceof N4JSResource && !(input as N4JSResource).isFullyProcessed) { - // Cancellation occurred likely before all validations completed, thus can't assume absence of errors. - // Cancellation may result in exit via normal control-flow (this case) or via exceptional control-flow (see exception handler below) - warnDueToCancelation(input, null); - return false; - } - - val Iterable errors = issues.filter[severity == ERROR]; - if (errors.isEmpty()) { - return true - } - if (logger.isDebugEnabled) { - errors.forEach[logger.debug(input.URI + " " + it.message + " " + it.severity + " @L_" + it.lineNumber + " ")] - } - return false - } - - private def void warnDueToCancelation(Resource input, Throwable exc) { - val msg = "User canceled the validation of " + input.URI + ". Will not compile."; - if (null === exc) { - logger.warn(msg) - } else { - logger.warn(msg,exc) - } - } - - /** @return true iff the current project has a project type that is supposed to generate code. */ - private def boolean isGenerateProjectType(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) { - val project = ws.findProjectContaining(n4jsSourceURI); - if (project !== null) { - val projectType = project.getType(); - if (N4JSGlobals.PROJECT_TYPES_WITHOUT_GENERATION.contains(projectType)) { - return false; - } - } - - return true; - } - - /** @return true iff the given resource does not lie within the output folder. */ - private def boolean isOutsideOfOutputFolder(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) { - return !containmentHelper.isContainedInOutputFolder(ws, n4jsSourceURI); - } - - /** @return true iff the output folder of the given n4js resource is not contained by a source container. */ - private def boolean isOutputNotInSourceContainer(N4JSWorkspaceConfigSnapshot ws, URI n4jsSourceURI) { - val project = ws.findProjectContaining(n4jsSourceURI); - if (project !== null) { - return !containmentHelper.isOutputContainedInSourceContainer(ws, project) - } else { - return false; - } - } - - /** - * Actual generation to be overridden by subclasses. - */ - protected def void internalDoGenerate(N4JSWorkspaceConfigSnapshot ws, Resource resource, GeneratorOption[] options, IFileSystemAccess access) - - /** - * Returns the name of the target file (without path) to which the source is to be compiled to. - * Default implementation returns a configured project Name with version + file name + extension. - * E.g., "proj-0.0.1/p/A.js" for a file A in proj. - * - * Convenience method, to provide all necessary API for the sub-classes. - * Delegates to {@link ResourceNameComputer#getTargetFileName}. - */ - protected def String getTargetFileName(Resource n4jsSourceFile, String compiledFileExtension) { - return resourceNameComputer.generateFileDescriptor(n4jsSourceFile, compiledFileExtension) - } - - /** - * Convenient access to the Script-Element - */ - protected def rootElement(Resource resource) { - resource.contents.filter(Script).head - } - - /** The file-extension of the compiled result */ - protected def String getCompiledFileExtension(Resource input) { - preferenceAccess.getPreference(input, getCompilerID(), CompilerProperties.COMPILED_FILE_EXTENSION, - getDefaultDescriptor()) - } - - /** The file-extension of the source-map to the compiled result */ - protected def String getCompiledFileSourceMapExtension(Resource input) { - preferenceAccess.getPreference(input, getCompilerID(), CompilerProperties.COMPILED_FILE_SOURCEMAP_EXTENSION, - getDefaultDescriptor()) - } - - /** Adjust output-path of the generator to match the N4JS projects-settings. */ - private def void updateOutputPath(N4JSWorkspaceConfigSnapshot ws, IFileSystemAccess fsa, String compilerID, Resource input) { - val outputPath = getOutputPath(ws, input.URI) ?: N4JSLanguageConstants.DEFAULT_PROJECT_OUTPUT; - if (fsa instanceof AbstractFileSystemAccess) { - val conf = fsa.outputConfigurations.get(compilerID) - if (conf !== null) { - conf.setOutputDirectory(outputPath) - } - } - } - - private static def String getOutputPath(N4JSWorkspaceConfigSnapshot ws, URI nestedLocation) { - return ws.findProjectContaining(nestedLocation)?.getProjectDescription()?.getOutputPath(); - } - - /** Navigation from the generated output-location to the location of the input-resource */ - protected def Path calculateNavigationFromOutputToSourcePath(N4JSWorkspaceConfigSnapshot ws, IFileSystemAccess fsa, String compilerID, N4JSResource input) { - // --- Project locations --- - val project = ws.findProjectContaining(input.URI); - - // /home/user/workspace/Project/ - val projectPath = project.pathAsFileURI.toFileSystemPath - // platform:/resource/Project/ - val projectLocURI = project.pathAsFileURI.withTrailingPathDelimiter.toURI - - // --- output locations --- - // src-gen - val outputPath = project.outputPath - // Project/a/b/c - val outputRelativeLocation = getOutputRelativeLocation(input) - - // --- source locations --- - // src/a/b/c - val inputURI = uriExtensions.withEmptyAuthority(input.URI); - val completetSourceURI = inputURI.trimSegments(1).deresolve(projectLocURI) - var completetSource = URIUtils.toFile(completetSourceURI).toString(); - - // Handling case when source container is the project root itself. (Sources { source { '.' } }) - if (null === completetSource && project.pathAsFileURI.toURI === input.URI.trimSegments(1)) { - completetSource = projectPath.toFile.absolutePath; - } - - // /home/user/workspace/Project/src-gen/a/b/c - val fullOutpath = projectPath.resolve(outputPath).normalize.resolve(outputRelativeLocation).normalize - // /home/user/workspace/Project/src/a/b/c - val fullSourcePath = projectPath.resolve(completetSource).normalize - - // ../../../../../../src/a/b/c - val rel = fullOutpath.relativize(fullSourcePath) - - return rel - } - - /** - * Calculates local output path for a given resource. - * Depending on the configuration this path can be in various forms, {@code Project-1.0.0/a/b/c/}, - * {@code Project/a/b/c/} or just {@code a/b/c/} - * - */ - private def Path getOutputRelativeLocation(N4JSResource input){ - val uri = uriExtensions.withEmptyAuthority(input.URI); - // Project/a/b/c/Input.XX - val localOutputFilePath = Paths.get(resourceNameComputer.generateFileDescriptor(input, uri, ".XX")) - - // if calculated path has just one element, e.g. "Input.XX" - // then local path segment is empty - if(localOutputFilePath.nameCount<2){ - return Paths.get("") - } - - //otherwise strip resource to get local path, i.e. Project/a/b/c/Input.XX => Project/a/b/c/ - return localOutputFilePath.subpath(0,localOutputFilePath.nameCount - 1) - } - - /** - * Convenience for {@link AbstractSubGenerator#calculateOutputDirectory(String, String)}, - * uses default compiler ID. - * - * TODO IDE-1487 currently there is no notion of default compiler. We fake call to the ES5 sub generator. - */ - def final static String calculateProjectBasedOutputDirectory(N4JSProjectConfigSnapshot project, boolean includeProjectName) { - return if (includeProjectName) project.packageName + "/" + project.outputPath else project.outputPath; - } - - /** Access to compiler ID */ - def abstract String getCompilerID() - - /** Access to compiler descriptor */ - def abstract CompilerDescriptor getDefaultDescriptor() - - /** Answers: Is this compiler activated for the input at hand? */ - def boolean isActive(Resource input) { - return Boolean.valueOf(preferenceAccess.getPreference(input, getCompilerID(), CompilerProperties.IS_ACTIVE, getDefaultDescriptor())) - } - - /** Checking the availability of a static polyfill, which will override the compilation of this module. - **/ - def boolean isNotStaticallyPolyfilled(Resource resource) { - // val TModule tmodule = (N4JSResource::getModule(resource) ); // for some reason xtend cannot see static getModule ?! - if (resource instanceof N4JSResource) { - val TModule tmodule = N4JSResource.getModule(resource) - // 1. resource must be StaticPolyfillAware and - // 2. there must exist a polyfilling instance (second instance with same fqn) - if (tmodule.isStaticPolyfillAware) { - // search for second instance. - if (staticPolyfillHelper.hasStaticPolyfill(resource)) { - return false; - } - } - } - return true; - } - - /** - * Checking if this resource represents a static polyfill, which will contribute to a filled resource. - **/ - private def boolean isStaticPolyfillingModule(Resource resource) { - val TModule tmodule = (N4JSResource.getModule(resource)); - if (null !== tmodule) { - return tmodule.isStaticPolyfillModule; - } - return false; - } - - /** - * - * @return true if the composite generator is applicable to the given resource and false otherwise. - */ - override boolean isApplicableTo(Resource input) { - return shouldBeCompiled(input, null); - } - - /** - * Depending on the file-extension, determines if the given resource requires actual transpilation as opposed to - * simply copying the source file to the output folder. - * - * @param eResource - * N4JS resource to check. - * @return true if the code requires transpilation. - */ - def protected boolean justCopy(Resource eResource) { - val resourceType = ResourceType.getResourceType(eResource); - return !(resourceType.equals(ResourceType.N4JS) || resourceType.equals(ResourceType.N4JSX) - || resourceType.equals(ResourceType.N4JSD)); - } - - /** - * Take the content of resource and copy it over to the output folder without any transformation. - * - * @param resource - * JS-code snippet which will be treated as text. - * @param fsa - * file system access - */ - def protected void copyWithoutTranspilation(Resource resource, IFileSystemAccess fsa) { - val outCode = new StringWriter(); - // get script - val script = resource.getContents().get(0); - - // obtain text - val scriptAsText = NodeModelUtils.getNode(script).getRootNode().getText(); - - // write - val decorated = scriptAsText.toString(); - outCode.write(decorated); - val filename = resourceNameComputer.generateFileDescriptor(resource, URIUtils.fileExtension(resource.getURI())); - fsa.generateFile(filename, compilerID, outCode.toString()); - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/GeneratorExceptionHandler.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/GeneratorExceptionHandler.java similarity index 58% rename from plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/GeneratorExceptionHandler.xtend rename to plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/GeneratorExceptionHandler.java index 2d21c7328c..2a58482697 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/GeneratorExceptionHandler.xtend +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/generator/GeneratorExceptionHandler.java @@ -8,20 +8,20 @@ * Contributors: * NumberFour AG - Initial API and implementation */ -package org.eclipse.n4js.generator +package org.eclipse.n4js.generator; /** * Code generator exception handler. */ -class GeneratorExceptionHandler { +public class GeneratorExceptionHandler { /** - * Prints provided message to the out stream, and the stack trace of the provided cause. - * Afterwards throws new {@link GeneratorException} wrapping provided cause. + * Prints provided message to the out stream, and the stack trace of the provided cause. Afterwards throws new + * {@link GeneratorException} wrapping provided cause. */ - def handleError(String message, Throwable cause) { - println(message) - cause.printStackTrace - throw new GeneratorException( message, cause); + public void handleError(String message, Throwable cause) { + System.out.println(message); + cause.printStackTrace(); + throw new GeneratorException(message, cause); } } diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonBuilder.java new file mode 100644 index 0000000000..c91ec98e79 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonBuilder.java @@ -0,0 +1,380 @@ +/** + * 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.packagejson; + +import static com.google.common.base.Optional.fromNullable; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.eclipse.n4js.json.JSON.JSONDocument; +import org.eclipse.n4js.json.model.utils.JSONModelUtils; +import org.eclipse.n4js.packagejson.projectDescription.ProjectType; +import org.eclipse.n4js.packagejson.projectDescription.SourceContainerType; +import org.eclipse.n4js.utils.ProjectDescriptionLoader; +import org.eclipse.n4js.workspace.utils.N4JSPackageName; + +/** + * Convenient builder for creating the N4JS package.json compliant {@link JSONDocument} model instances or file + * content.# + * + * Provides support for most supported N4JS-specific package.json properties. See {@link ProjectDescriptionLoader} for + * details on all supported properties. + */ +@SuppressWarnings("hiding") +public class PackageJsonBuilder { + + /** + * Creates a new builder instance with a default project type of {@link ProjectType#VALIDATION}. + */ + public static PackageJsonBuilder newBuilder() { + return new PackageJsonBuilder(ProjectType.VALIDATION); // use project type 'validation' + } + + /** + * Creates a new builder instance without a default project type. Use this for creating a plain package.json without + * an "n4js" section. + */ + public static PackageJsonBuilder newPlainBuilder() { + return new PackageJsonBuilder(null); + } + + private String projectName; + private ProjectType type; + private String version; + private Boolean _private; + + private final SortedMap dependencies; + private final SortedMap devDependencies; + + private String vendorId; + private String vendorName; + + private String output; + + private final Collection providedRLs; + private final Collection requiredRLs; + private final Collection testedProjects; + private String extendedRE; + private final Collection implementedProjects; + private String implementationId; + + private final Map sourceContainers; + + private final Collection workspaces; + + private PackageJsonBuilder(ProjectType defaultProjectType) { + type = defaultProjectType; + providedRLs = new ArrayList<>(); + requiredRLs = new ArrayList<>(); + dependencies = new TreeMap<>(); + devDependencies = new TreeMap<>(); + implementedProjects = new ArrayList<>(); + testedProjects = new ArrayList<>(); + sourceContainers = new HashMap<>(); + workspaces = new ArrayList<>(); + } + + /** + * Builds the N4JS package.json file contents for a project with the given name. + * + * @return the N4JS package.json file contents as a string. + */ + public String build() { + JSONDocument document = this.buildModel(); + return JSONModelUtils.serializeJSON(document); + } + + /** + * Builds the N4JS package.json {@code JSONDocument} model representation. + * + * @return the N4JS package.json {@code JSONDocument} representation. + */ + public JSONDocument buildModel() { + return PackageJsonContentProvider.getModel( + fromNullable(projectName), + fromNullable(version), + fromNullable(_private), + workspaces, + fromNullable(type), + fromNullable(vendorId), + fromNullable(vendorName), + fromNullable(output), + fromNullable(extendedRE), + dependencies, + devDependencies, + providedRLs, + requiredRLs, + fromNullable(implementationId), + implementedProjects, + testedProjects, + sourceContainers); + } + + /** + * Sets the project name and returns with the builder. + * + * @param name + * The N4JS project name. + * @return the builder. + */ + public PackageJsonBuilder withName(String name) { + this.projectName = checkNotNull(name); + return this; + } + + /** + * Sets the project version and returns with the builder. + * + * @param version + * The project version. + * @return the builder. + */ + public PackageJsonBuilder withVersion(String version) { + this.version = checkNotNull(version); + return this; + } + + /** + * Adds top-level property "private" to the package.json, with the given value. + */ + public PackageJsonBuilder withPrivate(boolean _private) { + this._private = _private; + return this; + } + + /** + * Sets the project type and returns with the builder. + * + * @param type + * the N4JS project type. + * @return the builder. + */ + public PackageJsonBuilder withType(ProjectType type) { + this.type = checkNotNull(type); + return this; + } + + /** + * Sets the vendorId and returns with the builder. + * + * @param vendorId + * The project's vendor ID. + * @return the builder. + */ + public PackageJsonBuilder withVendorId(String vendorId) { + this.vendorId = vendorId; + return this; + } + + /** + * Sets the vendor name and returns with the builder. + * + * @param vendorName + * The project's vendor name. + * @return the builder. + */ + public PackageJsonBuilder withVendorName(String vendorName) { + this.vendorName = vendorName; + return this; + } + + /** + * Sets the output folder and returns with the builder. + * + * @param output + * The project's vendor name. + * @return the builder. + */ + public PackageJsonBuilder withOutput(String output) { + this.output = output; + return this; + } + + /** + * Adds a source container with the given folder specifier and type. + * + * @param type + * The source container type. + * @param path + * The source container path. + */ + public PackageJsonBuilder withSourceContainer(SourceContainerType type, String path) { + this.sourceContainers.put(checkNotNull(type), checkNotNull(path)); + return this; + } + + /** + * Sets the extended runtime environment on the builder. + * + * @param extendedRE + * the extended runtime environment. Optional. Can be {@code null}. + * @return the builder. + */ + public PackageJsonBuilder withExtendedRE(String extendedRE) { + this.extendedRE = extendedRE; + return this; + } + + /** + * Adds a new provided runtime library to the current builder. + * + * @param providedRL + * the provided runtime library to add to the current builder. Cannot be {@code null}. + * @return the builder. + */ + public PackageJsonBuilder withProvidedRL(String providedRL) { + providedRLs.add(checkNotNull(providedRL)); + return this; + } + + /** + * Adds a new required runtime library to the current builder. + * + * This method will add the required runtime library both to the "requiredRuntimeLibraries" section, as well as the + * "dependencies" section. + * + * @param requiredRL + * the required runtime library to add to the current builder. Cannot be {@code null}. + * @return the builder. + */ + public PackageJsonBuilder withRequiredRL(String requiredRL) { + requiredRLs.add(checkNotNull(requiredRL)); + // also add to dependencies + dependencies.put(requiredRL, "*"); + return this; + } + + /** + * Adds a new required runtime library with the given version constraint. + * + * This method will add the required runtime library both to the "requiredRuntimeLibraries" section, as well as the + * "dependencies" section. + * + * @param requiredRL + * The project id of the required runtime library. + * @param versionConstraint + * The version constraint to be used in the dependency section. + */ + public PackageJsonBuilder withRequiredRL(String requiredRL, String versionConstraint) { + requiredRLs.add(checkNotNull(requiredRL)); + // also add to dependencies + dependencies.put(requiredRL, versionConstraint); + return this; + } + + /** + * Adds a new project dependency to the current builder. + * + * @param projectDependency + * the project dependency to add to the current builder. Cannot be {@code null}. + * @return the builder. + */ + public PackageJsonBuilder withDependency(N4JSPackageName projectDependency) { + dependencies.put(checkNotNull(projectDependency).getRawName(), "*"); + return this; + } + + /** + * Adds a new project dependency to the current builder. + * + * @param projectDependency + * The project dependency to add to the current builder. Cannot be {@code null}. + * @param versionConstraint + * The version constraint of the added project dependency. + * + * @return the builder. + */ + public PackageJsonBuilder withDependency(N4JSPackageName projectDependency, String versionConstraint) { + dependencies.put(checkNotNull(projectDependency).getRawName(), checkNotNull(versionConstraint)); + return this; + } + + /** + * Adds a new project devDependency to the current builder. + * + * @param projectDevDependency + * the project devDependency to add to the current builder. Cannot be {@code null}. + * @return the builder. + */ + public PackageJsonBuilder withDevDependency(N4JSPackageName projectDevDependency) { + devDependencies.put(checkNotNull(projectDevDependency).getRawName(), "*"); + return this; + } + + /** + * Adds a new project devDependency to the current builder. + * + * @param projectDevDependency + * The project devDependency to add to the current builder. Cannot be {@code null}. + * @param versionConstraint + * The version constraint of the added project devDependency. + * + * @return the builder. + */ + public PackageJsonBuilder withDevDependency(String projectDevDependency, String versionConstraint) { + devDependencies.put(checkNotNull(projectDevDependency), checkNotNull(versionConstraint)); + return this; + } + + /** + * Adds the given project id to the list of tested projects. + * + * This method also adds the given tested project as project dependency. + */ + public PackageJsonBuilder withTestedProject(String testedProject) { + dependencies.put(checkNotNull(testedProject), "*"); + testedProjects.add(checkNotNull(testedProject)); + return this; + } + + /** + * Sets the implementation id to the current builder. + * + * @param implementationId + * id for the implementations to choose from. + * @return the builder. + */ + public PackageJsonBuilder withImplementationId(String implementationId) { + this.implementationId = checkNotNull(implementationId); + return this; + } + + /** + * Adds a project to the list of implemented Projects. + * + * @param implementationAPI + * id of the implemented API + * @return the builder. + */ + public PackageJsonBuilder withImplementedProject(String implementationAPI) { + implementedProjects.add(checkNotNull(implementationAPI)); + return this; + } + + /** Adds top-level property "workspaces" to the package.json, using an array with the given strings as value. */ + public PackageJsonBuilder withWorkspaces(String... workspaces) { + this.workspaces.addAll(Arrays.asList(workspaces)); + return this; + } + + @Override + public String toString() { + return "!!! This is just a preview of the N4JS package.json file !!!\n" + this.build(); + } + +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonBuilder.xtend deleted file mode 100644 index b85a94da8c..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonBuilder.xtend +++ /dev/null @@ -1,358 +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.packagejson - -import java.util.Collection -import java.util.Map -import java.util.SortedMap -import java.util.TreeMap -import org.eclipse.n4js.json.JSON.JSONDocument -import org.eclipse.n4js.json.model.utils.JSONModelUtils -import org.eclipse.n4js.packagejson.projectDescription.ProjectType -import org.eclipse.n4js.packagejson.projectDescription.SourceContainerType -import org.eclipse.n4js.utils.ProjectDescriptionLoader - -import static com.google.common.base.Optional.fromNullable -import static com.google.common.base.Preconditions.checkNotNull -import org.eclipse.n4js.workspace.utils.N4JSPackageName - -/** - * Convenient builder for creating the N4JS package.json compliant {@link JSONDocument} model - * instances or file content.# - * - * Provides support for most supported N4JS-specific package.json properties. - * See {@link ProjectDescriptionLoader} for details on all supported properties. - */ -public class PackageJsonBuilder { - - /** - * Creates a new builder instance with a default project type of {@link ProjectType#VALIDATION}. - */ - public static def PackageJsonBuilder newBuilder() { - return new PackageJsonBuilder(ProjectType.VALIDATION); // use project type 'validation' - } - - /** - * Creates a new builder instance without a default project type. Use this for creating a - * plain package.json without an "n4js" section. - */ - public static def PackageJsonBuilder newPlainBuilder() { - return new PackageJsonBuilder(null); - } - - private String projectName; - private ProjectType type; - private String version; - private Boolean _private; - - private SortedMap dependencies; - private SortedMap devDependencies; - - private String vendorId; - private String vendorName; - - private String output; - - private Collection providedRLs; - private Collection requiredRLs; - private Collection testedProjects; - private String extendedRE; - private Collection implementedProjects; - private String implementationId; - - private Map sourceContainers; - - private Collection workspaces; - - private new(ProjectType defaultProjectType) { - type = defaultProjectType; - providedRLs = newArrayList - requiredRLs = newArrayList - dependencies = new TreeMap(); - devDependencies = new TreeMap(); - implementedProjects = newArrayList - testedProjects = newArrayList; - sourceContainers = newHashMap; - workspaces = newArrayList; - } - - /** - * Builds the N4JS package.json file contents for a project with the given name. - * - * @return the N4JS package.json file contents as a string. - */ - def String build() { - val document = this.buildModel(); - return JSONModelUtils.serializeJSON(document); - } - - /** - * Builds the N4JS package.json {@code JSONDocument} model representation. - * - * @return the N4JS package.json {@code JSONDocument} representation. - */ - def JSONDocument buildModel() { - return PackageJsonContentProvider.getModel( - fromNullable(projectName), - fromNullable(version), - fromNullable(_private), - workspaces, - fromNullable(type), - fromNullable(vendorId), - fromNullable(vendorName), - fromNullable(output), - fromNullable(extendedRE), - dependencies, - devDependencies, - providedRLs, - requiredRLs, - fromNullable(implementationId), - implementedProjects, - testedProjects, - sourceContainers); - } - - /** - * Sets the project name and returns with the builder. - * - * @param type The N4JS project name. - * @return the builder. - */ - def PackageJsonBuilder withName(String name) { - this.projectName = checkNotNull(name) - return this; - } - - /** - * Sets the project version and returns with the builder. - * - * @param version The project version. - * @return the builder. - */ - def PackageJsonBuilder withVersion(String version) { - this.version = checkNotNull(version) - return this; - } - - /** - * Adds top-level property "private" to the package.json, with the given value. - */ - def PackageJsonBuilder withPrivate(boolean _private) { - this._private = _private; - return this; - } - - /** - * Sets the project type and returns with the builder. - * - * @param type the N4JS project type. - * @return the builder. - */ - def PackageJsonBuilder withType(ProjectType type) { - this.type = checkNotNull(type) - return this; - } - - /** - * Sets the vendorId and returns with the builder. - * - * @param type The project's vendor ID. - * @return the builder. - */ - def PackageJsonBuilder withVendorId(String vendorId) { - this.vendorId = vendorId; - return this; - } - - /** - * Sets the vendor name and returns with the builder. - * - * @param type The project's vendor name. - * @return the builder. - */ - def PackageJsonBuilder withVendorName(String vendorName) { - this.vendorName = vendorName; - return this; - } - - /** - * Sets the output folder and returns with the builder. - * - * @param type The project's vendor name. - * @return the builder. - */ - def PackageJsonBuilder withOutput(String output) { - this.output = output; - return this; - } - - /** - * Adds a source container with the given folder specifier and type. - * - * @param type The source container type. - * @param path The source container path. - */ - def PackageJsonBuilder withSourceContainer(SourceContainerType type, String path) { - this.sourceContainers.put(checkNotNull(type), checkNotNull(path)); - return this; - } - - /** - * Sets the extended runtime environment on the builder. - * @param extendedRE the extended runtime environment. Optional. Can be {@code null}. - * @return the builder. - */ - def PackageJsonBuilder withExtendedRE(String extendedRE) { - this.extendedRE = extendedRE - return this; - } - - /** - * Adds a new provided runtime library to the current builder. - * @param providedRL the provided runtime library to add to the current builder. Cannot be {@code null}. - * @return the builder. - */ - def PackageJsonBuilder withProvidedRL(String providedRL) { - providedRLs.add(checkNotNull(providedRL)) - return this; - } - - /** - * Adds a new required runtime library to the current builder. - * - * This method will add the required runtime library both to the "requiredRuntimeLibraries" section, - * as well as the "dependencies" section. - * - * @param requiredRL the required runtime library to add to the current builder. Cannot be {@code null}. - * @return the builder. - */ - def PackageJsonBuilder withRequiredRL(String requiredRL) { - requiredRLs.add(checkNotNull(requiredRL)) - // also add to dependencies - dependencies.put(requiredRL, "*") - return this; - } - - /** - * Adds a new required runtime library with the given version constraint. - * - * This method will add the required runtime library both to the "requiredRuntimeLibraries" section, - * as well as the "dependencies" section. - * - * @param requiredRL The project id of the required runtime library. - * @param versionConstraint The version constraint to be used in the dependency section. - */ - def PackageJsonBuilder withRequiredRL(String requiredRL, String versionConstraint) { - requiredRLs.add(checkNotNull(requiredRL)) - // also add to dependencies - dependencies.put(requiredRL, versionConstraint) - return this; - } - - /** - * Adds a new project dependency to the current builder. - * @param projectDependency the project dependency to add to the current builder. Cannot be {@code null}. - * @return the builder. - */ - def PackageJsonBuilder withDependency(N4JSPackageName projectDependency) { - dependencies.put(checkNotNull(projectDependency).rawName, "*") - return this; - } - - /** - * Adds a new project dependency to the current builder. - * - * @param projectDependency The project dependency to add to the current builder. Cannot be {@code null}. - * @param versionConstraint The version constraint of the added project dependency. - * - * @return the builder. - */ - def PackageJsonBuilder withDependency(N4JSPackageName projectDependency, String versionConstraint) { - dependencies.put(checkNotNull(projectDependency).rawName, checkNotNull(versionConstraint)) - return this; - } - - /** - * Adds a new project devDependency to the current builder. - * @param projectDevDependency the project devDependency to add to the current builder. Cannot be {@code null}. - * @return the builder. - */ - def PackageJsonBuilder withDevDependency(N4JSPackageName projectDevDependency) { - devDependencies.put(checkNotNull(projectDevDependency).rawName, "*") - return this; - } - - /** - * Adds a new project devDependency to the current builder. - * - * @param projectDevDependency The project devDependency to add to the current builder. Cannot be {@code null}. - * @param versionConstraint The version constraint of the added project devDependency. - * - * @return the builder. - */ - def PackageJsonBuilder withDevDependency(String projectDevDependency, String versionConstraint) { - devDependencies.put(checkNotNull(projectDevDependency), checkNotNull(versionConstraint)) - return this; - } - - /** - * Adds the given project id to the list of tested projects. - * - * This method also adds the given tested project as project dependency. - */ - def PackageJsonBuilder withTestedProject(String testedProject) { - dependencies.put(checkNotNull(testedProject), "*"); - testedProjects.add(checkNotNull(testedProject)); - return this; - } - - /** - * Adds the given project id to the list of tested projects and to the list - * of dependencies with the given version constraint. - * - * This method also adds the given tested project as project dependency. - */ - def PackageJsonBuilder withTestedProject(String testedProject, String version) { - dependencies.put(checkNotNull(testedProject), "*"); - testedProjects.add(checkNotNull(testedProject)); - return this; - } - - /** Sets the implementation id to the current builder. - * @param implementationId id for the implementations to choose from. - * @return the builder. - */ - def PackageJsonBuilder withImplementationId(String implementationId) { - this.implementationId = checkNotNull(implementationId) - return this; - } - - /** Adds a project to the list of implemented Projects. - * NOTE: also call {@link withImplementationId()} - * @param project id of the implemented API - * @return the builder. - */ - def PackageJsonBuilder withImplementedProject(String implementationAPI) { - implementedProjects.add(checkNotNull(implementationAPI)) - return this; - } - - /** Adds top-level property "workspaces" to the package.json, using an array with the given strings as value. */ - def PackageJsonBuilder withWorkspaces(String... workspaces) { - this.workspaces.addAll(workspaces); - return this; - } - - override toString() { - return '!!! This is just a preview of the N4JS package.json file !!!\n' + this.build(); - } - -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonContentProvider.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonContentProvider.java new file mode 100644 index 0000000000..50206fa4ca --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonContentProvider.java @@ -0,0 +1,241 @@ +/** + * 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.packagejson; + +import static org.eclipse.n4js.packagejson.PackageJsonProperties.DEPENDENCIES; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.DEV_DEPENDENCIES; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.EXTENDED_RUNTIME_ENVIRONMENT; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.IMPLEMENTATION_ID; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.IMPLEMENTED_PROJECTS; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.N4JS; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.NAME; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.OUTPUT; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.PRIVATE; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.PROJECT_TYPE; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.PROVIDED_RUNTIME_LIBRARIES; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.REQUIRED_RUNTIME_LIBRARIES; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.SOURCES; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.TESTED_PROJECTS; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.VENDOR_ID; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.VENDOR_NAME; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.VERSION; +import static org.eclipse.n4js.packagejson.PackageJsonProperties.WORKSPACES_ARRAY; +import static org.eclipse.n4js.packagejson.PackageJsonUtils.getSourceContainerTypeStringRepresentation; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.groupBy; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.SortedMap; + +import org.eclipse.n4js.json.JSON.JSONArray; +import org.eclipse.n4js.json.JSON.JSONDocument; +import org.eclipse.n4js.json.JSON.JSONFactory; +import org.eclipse.n4js.json.JSON.JSONObject; +import org.eclipse.n4js.json.JSON.JSONStringLiteral; +import org.eclipse.n4js.json.JSON.NameValuePair; +import org.eclipse.n4js.json.model.utils.JSONModelUtils; +import org.eclipse.n4js.packagejson.projectDescription.ProjectType; +import org.eclipse.n4js.packagejson.projectDescription.SourceContainerType; + +import com.google.common.base.Optional; + +/** + * Class for providing the content of N4JS-specific package.json files. + * + * Use {@link PackageJsonBuilder} for creating package.json model instances and file content. + */ +class PackageJsonContentProvider { + + /** + * Creates and returns with the N4JS package.json {@link JSONDocument} representation based on the given arguments. + * + * @param projectName + * the N4JS project name of the project (cf. name). + * @param version + * The declared version of the project. + * @param type + * The type of the N4JS project. + * @param vendorId + * The vendorId to use. + * @param vendorName + * The name of the vendor as string. + * @param output + * The relative output folder location. + * @param extendedRE + * The optional extended runtime environment. + * @param dependencies + * A map of dependencies of the project (maps dependencies to their version constraints). + * @param providedRL + * An iterable of provided runtime libraries. + * @param requiredRL + * An iterable of required runtime libraries. + * @param implementationId + * The implementationId of the project. + * @param testedProjects + * A list of all projects that are being tested. + * @param sourceContainers + * A map of all source containers of the project. + * + * @return the N4JS package.json content as a string. + */ + static JSONDocument getModel( + Optional projectName, + Optional version, + Optional _private, + Iterable workspaces, + Optional type, + Optional vendorId, + Optional vendorName, + Optional output, + Optional extendedRE, + SortedMap dependencies, + SortedMap devDependencies, + Iterable providedRL, + Iterable requiredRL, + Optional implementationId, + Iterable implementedProjects, + Iterable testedProjects, + Map sourceContainers) { + JSONObject root = JSONFactory.eINSTANCE.createJSONObject(); + + if (projectName.isPresent()) + JSONModelUtils.addProperty(root, NAME.name, projectName.get()); + + if (version.isPresent()) + JSONModelUtils.addProperty(root, VERSION.name, version.get()); + + if (_private.isPresent()) { + JSONModelUtils.addProperty(root, PRIVATE.name, + JSONModelUtils.createBooleanLiteral(_private.get())); + } + + if (workspaces.iterator().hasNext()) { + JSONModelUtils.addProperty(root, WORKSPACES_ARRAY.name, + JSONModelUtils.createStringArray(workspaces)); + } + + // add "dependencies" section + if (!dependencies.isEmpty()) { + JSONObject dependenciesValue = createDependenciesValue(dependencies); + JSONModelUtils.addProperty(root, DEPENDENCIES.name, dependenciesValue); + } + + // add "devDependencies" section + if (!devDependencies.isEmpty()) { + JSONObject devDependenciesValue = createDependenciesValue(devDependencies); + JSONModelUtils.addProperty(root, DEV_DEPENDENCIES.name, devDependenciesValue); + } + + // create "n4js" section (will be added below iff it will be non-empty) + JSONObject n4jsRoot = JSONFactory.eINSTANCE.createJSONObject(); + + // project type + if (type.isPresent()) { + String projectTypeStr = PackageJsonUtils.getProjectTypeStringRepresentation(type.get()); + JSONModelUtils.addProperty(n4jsRoot, PROJECT_TYPE.name, projectTypeStr); + } + + // add vendor related properties + if (vendorId.isPresent()) + JSONModelUtils.addProperty(n4jsRoot, VENDOR_ID.name, vendorId.get()); + if (vendorName.isPresent()) + JSONModelUtils.addProperty(n4jsRoot, VENDOR_NAME.name, vendorName.get()); + + // add sources section + if (!sourceContainers.isEmpty()) { + JSONObject sourcesSection = JSONFactory.eINSTANCE.createJSONObject(); + JSONModelUtils.addProperty(n4jsRoot, SOURCES.name, sourcesSection); + + // add sources sub-sections + List> srcConts = new ArrayList<>(sourceContainers.entrySet()); + // sort by container type + Collections.sort(srcConts, + Comparator.comparing(e -> getSourceContainerTypeStringRepresentation(e.getKey()))); + // group by source container type + Map>> srcContsByPath = groupBy(srcConts, + e -> e.getKey()); + + // add source container sub-section for each specified source container type + for (SourceContainerType sct : srcContsByPath.keySet()) { + List> paths = srcContsByPath.get(sct); + JSONArray typeSectionArray = JSONFactory.eINSTANCE.createJSONArray(); + JSONModelUtils.addProperty(sourcesSection, + PackageJsonUtils.getSourceContainerTypeStringRepresentation(sct), typeSectionArray); + List pathLiterals = toList( + map(paths, pathEntry -> JSONModelUtils.createStringLiteral(pathEntry.getValue()))); + typeSectionArray.getElements().addAll(pathLiterals); + } + } + + // add output folder + if (output.isPresent()) + JSONModelUtils.addProperty(n4jsRoot, OUTPUT.name, output.get()); + + // add provided and required runtime libraries if given + if (providedRL.iterator().hasNext()) { + JSONModelUtils.addProperty(n4jsRoot, PROVIDED_RUNTIME_LIBRARIES.name, + JSONModelUtils.createStringArray(providedRL)); + } + if (requiredRL.iterator().hasNext()) { + JSONModelUtils.addProperty(n4jsRoot, REQUIRED_RUNTIME_LIBRARIES.name, + JSONModelUtils.createStringArray(requiredRL)); + } + + if (extendedRE.isPresent()) { + JSONModelUtils.addProperty(n4jsRoot, EXTENDED_RUNTIME_ENVIRONMENT.name, + extendedRE.get()); + } + if (implementationId.isPresent()) { + JSONModelUtils.addProperty(n4jsRoot, IMPLEMENTATION_ID.name, + implementationId.get()); + } + + if (implementedProjects.iterator().hasNext()) { + JSONModelUtils.addProperty(n4jsRoot, IMPLEMENTED_PROJECTS.name, + JSONModelUtils.createStringArray(implementedProjects)); + } + + if (testedProjects.iterator().hasNext()) { + JSONModelUtils.addProperty(n4jsRoot, TESTED_PROJECTS.name, + JSONModelUtils.createStringArray(testedProjects)); + } + + // add "n4js" section (if non-empty) + if (!n4jsRoot.getNameValuePairs().isEmpty()) { + JSONModelUtils.addProperty(root, N4JS.name, n4jsRoot); + } + + // finally serialize as JSONDocument + JSONDocument document = JSONFactory.eINSTANCE.createJSONDocument(); + document.setContent(root); + + return document; + } + + private static JSONObject createDependenciesValue(Map dependencies) { + JSONObject dependenciesValue = JSONFactory.eINSTANCE.createJSONObject(); + + List map = toList(map(dependencies.entrySet(), e -> { + NameValuePair pair = JSONFactory.eINSTANCE.createNameValuePair(); + pair.setName(e.getKey()); + pair.setValue(JSONModelUtils.createStringLiteral(e.getValue())); + return pair; + })); + dependenciesValue.getNameValuePairs().addAll(map); + return dependenciesValue; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonContentProvider.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonContentProvider.xtend deleted file mode 100644 index 1b36e0aa57..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonContentProvider.xtend +++ /dev/null @@ -1,210 +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.packagejson - -import com.google.common.base.Optional -import java.util.Map -import java.util.SortedMap -import org.eclipse.n4js.json.JSON.JSONArray -import org.eclipse.n4js.json.JSON.JSONDocument -import org.eclipse.n4js.json.JSON.JSONFactory -import org.eclipse.n4js.json.JSON.JSONObject -import org.eclipse.n4js.json.model.utils.JSONModelUtils -import org.eclipse.n4js.packagejson.projectDescription.ProjectType -import org.eclipse.n4js.packagejson.projectDescription.SourceContainerType - -import static org.eclipse.n4js.packagejson.PackageJsonProperties.DEPENDENCIES -import static org.eclipse.n4js.packagejson.PackageJsonProperties.DEV_DEPENDENCIES -import static org.eclipse.n4js.packagejson.PackageJsonProperties.EXTENDED_RUNTIME_ENVIRONMENT -import static org.eclipse.n4js.packagejson.PackageJsonProperties.IMPLEMENTATION_ID -import static org.eclipse.n4js.packagejson.PackageJsonProperties.IMPLEMENTED_PROJECTS -import static org.eclipse.n4js.packagejson.PackageJsonProperties.N4JS -import static org.eclipse.n4js.packagejson.PackageJsonProperties.NAME -import static org.eclipse.n4js.packagejson.PackageJsonProperties.OUTPUT -import static org.eclipse.n4js.packagejson.PackageJsonProperties.PRIVATE -import static org.eclipse.n4js.packagejson.PackageJsonProperties.PROJECT_TYPE -import static org.eclipse.n4js.packagejson.PackageJsonProperties.PROVIDED_RUNTIME_LIBRARIES -import static org.eclipse.n4js.packagejson.PackageJsonProperties.REQUIRED_RUNTIME_LIBRARIES -import static org.eclipse.n4js.packagejson.PackageJsonProperties.SOURCES -import static org.eclipse.n4js.packagejson.PackageJsonProperties.TESTED_PROJECTS -import static org.eclipse.n4js.packagejson.PackageJsonProperties.VENDOR_ID -import static org.eclipse.n4js.packagejson.PackageJsonProperties.VENDOR_NAME -import static org.eclipse.n4js.packagejson.PackageJsonProperties.VERSION -import static org.eclipse.n4js.packagejson.PackageJsonProperties.WORKSPACES_ARRAY - -/** - * Class for providing the content of N4JS-specific package.json files. - * - * Use {@link PackageJsonBuilder} for creating package.json model instances and file content. - */ -package class PackageJsonContentProvider { - - /** - * Creates and returns with the N4JS package.json {@link JSONDocument} representation - * based on the given arguments. - * - * @param projectName the N4JS project name of the project (cf. name). - * @param version The declared version of the project. - * @param type The type of the N4JS project. - * @param vendorId The vendorId to use. - * @param vendorName The name of the vendor as string. - * @param output The relative output folder location. - * @param extendedRE The optional extended runtime environment. - * @param dependencies A map of dependencies of the project (maps dependencies to their version constraints). - * @param providedRL An iterable of provided runtime libraries. - * @param requiredRL An iterable of required runtime libraries. - * @param implementationId The implementationId of the project. - * @param testedProject A list of all projects that are being tested. - * @param sourceContainers A map of all source containers of the project. - * - * @return the N4JS package.json content as a string. - */ - package static def JSONDocument getModel( - Optional projectName, - Optional version, - Optional _private, - Iterable workspaces, - Optional type, - Optional vendorId, - Optional vendorName, - Optional output, - Optional extendedRE, - SortedMap dependencies, - SortedMap devDependencies, - Iterable providedRL, - Iterable requiredRL, - Optional implementationId, - Iterable implementedProjects, - Iterable testedProjects, - Map sourceContainers - ) { - val JSONObject root = JSONFactory.eINSTANCE.createJSONObject(); - - if (projectName.present) - JSONModelUtils.addProperty(root, NAME.name, projectName.get()); - - if (version.present) - JSONModelUtils.addProperty(root, VERSION.name, version.get()); - - if (_private.present) { - JSONModelUtils.addProperty(root, PRIVATE.name, - JSONModelUtils.createBooleanLiteral(_private.get())); - } - - if (!workspaces.empty) { - JSONModelUtils.addProperty(root, WORKSPACES_ARRAY.name, - JSONModelUtils.createStringArray(workspaces)); - } - - // add "dependencies" section - if (!dependencies.empty) { - val dependenciesValue = createDependenciesValue(dependencies); - JSONModelUtils.addProperty(root, DEPENDENCIES.name, dependenciesValue); - } - - // add "devDependencies" section - if (!devDependencies.empty) { - val devDependenciesValue = createDependenciesValue(devDependencies); - JSONModelUtils.addProperty(root, DEV_DEPENDENCIES.name, devDependenciesValue); - } - - // create "n4js" section (will be added below iff it will be non-empty) - val JSONObject n4jsRoot = JSONFactory.eINSTANCE.createJSONObject(); - - // project type - if (type.present) { - val projectTypeStr = PackageJsonUtils.getProjectTypeStringRepresentation(type.get()); - JSONModelUtils.addProperty(n4jsRoot, PROJECT_TYPE.name, projectTypeStr); - } - - // add vendor related properties - if (vendorId.present) - JSONModelUtils.addProperty(n4jsRoot, VENDOR_ID.name, vendorId.get()); - if (vendorName.present) - JSONModelUtils.addProperty(n4jsRoot, VENDOR_NAME.name, vendorName.get()); - - // add sources section - if (!sourceContainers.empty) { - val JSONObject sourcesSection = JSONFactory.eINSTANCE.createJSONObject(); - JSONModelUtils.addProperty(n4jsRoot, SOURCES.name, sourcesSection); - - // add sources sub-sections - sourceContainers.entrySet - // sort by container type - .sortBy[ e | PackageJsonUtils.getSourceContainerTypeStringRepresentation(e.key) ] - // group by source container type - .groupBy[ e | e.key ] - // add source container sub-section for each specified source container type - .forEach[containerType, paths| - val JSONArray typeSectionArray = JSONFactory.eINSTANCE.createJSONArray(); - JSONModelUtils.addProperty(sourcesSection, - PackageJsonUtils.getSourceContainerTypeStringRepresentation(containerType), typeSectionArray); - val pathLiterals = paths.map[pathEntry | JSONModelUtils.createStringLiteral(pathEntry.value) ]; - typeSectionArray.getElements().addAll(pathLiterals); - ]; - } - - // add output folder - if (output.present) - JSONModelUtils.addProperty(n4jsRoot, OUTPUT.name, output.get()); - - // add provided and required runtime libraries if given - if (!providedRL.empty) { - JSONModelUtils.addProperty(n4jsRoot, PROVIDED_RUNTIME_LIBRARIES.name, - JSONModelUtils.createStringArray(providedRL)); - } - if (!requiredRL.empty) { - JSONModelUtils.addProperty(n4jsRoot, REQUIRED_RUNTIME_LIBRARIES.name, - JSONModelUtils.createStringArray(requiredRL)); - } - - if (extendedRE.isPresent) { - JSONModelUtils.addProperty(n4jsRoot, EXTENDED_RUNTIME_ENVIRONMENT.name, - extendedRE.get()); - } - if (implementationId.isPresent) { - JSONModelUtils.addProperty(n4jsRoot, IMPLEMENTATION_ID.name, - implementationId.get()); - } - - if (!implementedProjects.empty) { - JSONModelUtils.addProperty(n4jsRoot, IMPLEMENTED_PROJECTS.name, - JSONModelUtils.createStringArray(implementedProjects)); - } - - if (!testedProjects.empty) { - JSONModelUtils.addProperty(n4jsRoot, TESTED_PROJECTS.name, - JSONModelUtils.createStringArray(testedProjects)); - } - - // add "n4js" section (if non-empty) - if (!n4jsRoot.nameValuePairs.empty) { - JSONModelUtils.addProperty(root, N4JS.name, n4jsRoot); - } - - // finally serialize as JSONDocument - val JSONDocument document = JSONFactory.eINSTANCE.createJSONDocument(); - document.setContent(root); - - return document; - } - - private def static JSONObject createDependenciesValue(Map dependencies) { - val JSONObject dependenciesValue = JSONFactory.eINSTANCE.createJSONObject(); - dependenciesValue.nameValuePairs.addAll(dependencies.entrySet.map [ e | - val pair = JSONFactory.eINSTANCE.createNameValuePair(); - pair.name = e.key; - pair.value = JSONModelUtils.createStringLiteral(e.value); - return pair; - ]); - return dependenciesValue; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ASTProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ASTProcessor.java new file mode 100644 index 0000000000..20c34f159c --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ASTProcessor.java @@ -0,0 +1,563 @@ +/** + * 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.addCancelIndicator; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.getCancelIndicator; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.isCanceled; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.newRuleEnvironment; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.isASTNode; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.isIdentifiableSubtree; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filterNull; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.toIterable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.n4JS.Annotation; +import org.eclipse.n4js.n4JS.CatchBlock; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.ForStatement; +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.n4JS.FunctionDefinition; +import org.eclipse.n4js.n4JS.FunctionExpression; +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName; +import org.eclipse.n4js.n4JS.N4ClassifierDeclaration; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4JSASTUtils; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.n4JS.PropertyGetterDeclaration; +import org.eclipse.n4js.n4JS.PropertyMethodDeclaration; +import org.eclipse.n4js.n4JS.PropertyNameValuePair; +import org.eclipse.n4js.n4JS.PropertySetterDeclaration; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.n4JS.SetterDeclaration; +import org.eclipse.n4js.n4JS.ThisLiteral; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.YieldExpression; +import org.eclipse.n4js.resource.N4JSResource; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.SyntaxRelatedTElement; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.ts.types.TVariable; +import org.eclipse.n4js.ts.types.TypableElement; +import org.eclipse.n4js.ts.types.TypesPackage; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.utils.EcoreUtilN4; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.StaticPolyfillHelper; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.util.CancelIndicator; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * Main processor used during {@link N4JSPostProcessor post-processing} of N4JS resources. It controls the overall work + * flow of processing the AST, but does not do any actual work; instead, it delegates to the other processors: + *
    + *
  • {@link TypeProcessor}, which delegates further to + *
      + *
    • {@link PolyProcessor}, which delegates further to + *
        + *
      • {@link PolyProcessor_ArrayLiteral} + *
      • {@link PolyProcessor_ObjectLiteral} + *
      • {@link PolyProcessor_FunctionExpression} + *
      • {@link PolyProcessor_CallExpression} + *
      + *
    • {@link DestructureProcessor} + *
    + *
  • {@code TypeExpectedProcessor} (coming soon!) + *
  • {@link TypeDeferredProcessor} + *
+ */ +@Singleton +public class ASTProcessor extends AbstractProcessor { + + @Inject + private ComputedNameProcessor computedNameProcessor; + @Inject + private TypeProcessor typeProcessor; + @Inject + private TypeRefProcessor typeRefProcessor; + @Inject + private TypeDeferredProcessor typeDeferredProcessor; + @Inject + private CompileTimeExpressionProcessor compileTimeExpressionProcessor; + @Inject + private RuntimeDependencyProcessor runtimeDependencyProcessor; + @Inject + private StaticPolyfillHelper staticPolyfillHelper; + + /** + * Entry point for processing of the entire AST of the given resource. Will throw IllegalStateException if called + * more than once per N4JSResource. + *

+ * This method performs some preparatory tasks (e.g., creating an instance of {@link ASTMetaInfoCache}) and ensures + * consistency by tracking the 'isProcessing' state with try/finally; for actual processing, this method delegates + * to method {@link #processAST(RuleEnvironment, Script, ASTMetaInfoCache)}. + * + * @param resource + * may not be null. + * @param cancelIndicator + * may be null. + */ + public void processAST(N4JSResource resource, CancelIndicator cancelIndicator) { + if (resource == null) + throw new IllegalArgumentException("resource may not be null"); + + // the following is required, because typing may have been initiated by resolution of a proxy + // -> when traversing the AST, we will sooner or later try to resolve this same proxy, which would be + // interpreted as a cyclic proxy resolution by method LazyLinkingResource#getEObject(String,Triple) + resource.clearResolving(); + + log(0, "### processing resource: " + resource.getURI()); + + Script script = resource.getScript(); + // we're during post-processing, so cache should be available now + ASTMetaInfoCache cache = resource.getASTMetaInfoCacheVerifyContext(); + RuleEnvironment G = newRuleEnvironment(resource); + addCancelIndicator(G, cancelIndicator); + try { + processAST(G, script, cache); + } finally { + if (isCanceled(G)) { + log(0, "CANCELED by cancelIndicator"); + } + + if (isDEBUG_LOG_RESULT()) { + log(0, "### result for " + resource.getURI()); + log(4, resource.getScript(), cache); + } + log(0, "### done: " + resource.getURI()); + } + } + + /** + * First method to actually perform processing of the AST. This method defines the various processing phases. + *

+ * There exists a single "main phase" where 95% of processing happens (entry point for this main phase is method + * {@link #processSubtree(RuleEnvironment, EObject, ASTMetaInfoCache, int)}), plus a number of smaller phases before + * and after that where some special handling is performed. + */ + private void processAST(RuleEnvironment G, Script script, ASTMetaInfoCache cache) { + // phase 0: process compile-time expressions & computed property names (order is important) + for (Expression node : toIterable(filter(script.eAllContents(), Expression.class))) { + compileTimeExpressionProcessor.evaluateCompileTimeExpression(G, node, cache); + } + for (LiteralOrComputedPropertyName node : toIterable( + filter(script.eAllContents(), LiteralOrComputedPropertyName.class))) { + computedNameProcessor.processComputedPropertyName(node, cache); + } + + // phase 1: main processing + processSubtree(G, script, cache, 0); + // phase 2: processing of postponed subtrees + EObject eObj; + while ((eObj = cache.postponedSubTrees.poll()) != null) { + // note: we need to allow adding more postponed subtrees inside this loop! + processSubtree(G, eObj, cache, 0); + } + // phase 3: store runtime and load-time dependencies in TModule + runtimeDependencyProcessor.storeDirectRuntimeDependenciesInTModule(script, cache); + } + + /** + * Process given node and all of its direct and indirect children. + * + * @param node + * the root of the subtree to process; must be an AST node. + */ + void processSubtree(RuleEnvironment G, EObject node, ASTMetaInfoCache cache, int indentLevel) { + assertTrueIfRigid(cache, "argument 'node' must be an AST node", isASTNode(node)); + + log(indentLevel, "processing: " + getObjectInfo(node)); + + checkCanceled(G); + + // already done as part of a forward processing? + if (cache.forwardProcessedSubTrees.contains(node)) { + if (isDEBUG_LOG()) { + log(indentLevel, "(subtree already processed as a forward reference)"); + if (node instanceof TypableElement) { + log(indentLevel, cache.getTypeFailSafe((TypableElement) node)); + } + } + return; + } + if (cache.postponedSubTrees.contains(node)) { + // in case this happens, you can either: + // * not postpone this node, or + // * handle the postponed node later (not as part of a forward reference) + throw new IllegalStateException("eager processing of postponed subtree"); + } + + if (!cache.astNodesCurrentlyBeingTyped.add(node)) { + // this subtree is currently being processed + // (can happen, for example, if we are processing a member (e.g. field) and during that processing we + // encounter a reference to the containing class (e.g. in the initializer expression)) + if (isDEBUG_LOG()) { + log(indentLevel, "(subtree currently in progress - skipping)"); + } + return; + } + + try { + // process node itself - part 1 (before child processing) + processNode_preChildren(G, node, cache); + + // process the children + List children = childrenToBeProcessed(node); + for (EObject child : children) { + if (isPostponedNode(child)) { + // postpone + cache.postponedSubTrees.add(child); + } else { + // process now + processSubtree(G, child, cache, indentLevel + 1); + checkCanceled(G); + } + } + + // process node itself - part 2 (after child processing) + processNode_postChildren(G, node, cache, indentLevel); + + // we're done with this node, but make sure that all proxies have actually been resolved + // (this is important mainly for two reasons: (1) post-processing is often triggered by a call to + // N4JSResource#resolveLazyCrossReferences(CancelIndicator), so we have to guarantee that all lazy + // cross-references are actually resolved; (2) the type system may not resolve all proxies and some + // nodes are not typed at all (i.e. isTypableNode() returns false), so we have to enforce this here. + + // We also perform all processing, related to outgoing references from the current node at this point. + resolveAndProcessReferencesInNode(node, cache); + + } finally { + cache.astNodesCurrentlyBeingTyped.remove(node); + } + } + + private boolean isPostponedNode(EObject node) { + return isPostponedInitializer(node) + || N4JSASTUtils.isBodyOfFunctionOrFieldAccessor(node); + } + + /** + * Initializers are postponed iff: + *

    + *
  • Node is an initializer of a FormalParameter p,
  • + *
  • and p is part of a Poly FunctionExpression f,
  • + *
  • and p contains references to other FormalParameters of f, or f itself.
  • + *
+ */ + private boolean isPostponedInitializer(EObject node) { + boolean isPostponedInitializer = false; + EObject fp = node.eContainer(); + if (fp instanceof FormalParameter) { + FormalParameter fpar = (FormalParameter) fp; + if (node instanceof Expression) { + if (fpar.isHasInitializerAssignment()) { + EObject funDefObj = fpar.eContainer(); + // IdentifierRef in Initializers can cause cyclic dependencies + if (funDefObj instanceof FunctionExpression) { + FunctionExpression funDef = (FunctionExpression) funDefObj; + // Check if the initializer refers to other fpars + EList allFPars = funDef.getFpars(); + List allRefs = EcoreUtilN4.getAllContentsOfTypeStopAt(fpar, IdentifierRef.class, + N4JSPackage.Literals.FUNCTION_OR_FIELD_ACCESSOR__BODY); + + for (IdentifierRef ir : allRefs) { + Object id = ir.getId(); + if (id instanceof SyntaxRelatedTElement) { + id = ((SyntaxRelatedTElement) id) + .eGet(TypesPackage.eINSTANCE.getSyntaxRelatedTElement_AstElement(), false); + } + boolean idRefCausesCyclDep = allFPars.contains(id) // f(p, q=p) {} + || id instanceof VariableDeclaration + && ((VariableDeclaration) id).getExpression() == funDef; // f(p, q=f(1)) {} + if (idRefCausesCyclDep) { + isPostponedInitializer = true; + } + } + } + // In ObjectLiterals, the ThisLiteral in Initializers can cause cyclic dependencies + // TODO GH-1337 add support for spread operator + boolean thisLiteralCausesCyclDep = + // let o = { a:1, f(p=this.a) {} } + funDefObj instanceof PropertyMethodDeclaration + || + // let o = {a:2, f: function(p=this.a) {}} + funDefObj instanceof FunctionExpression + && funDefObj.eContainer() instanceof PropertyNameValuePair; + + if (thisLiteralCausesCyclDep) { + boolean containsThisLiteral = EcoreUtilN4.containsContentsOfTypeStopAt(fpar, ThisLiteral.class, + N4JSPackage.Literals.FUNCTION_OR_FIELD_ACCESSOR__BODY); + if (containsThisLiteral) { + isPostponedInitializer = true; + } + } + // If this check is not sufficient, we have to add more checks here. Note: Setters never have + // initializers. + } + } + } + return isPostponedInitializer; + } + + /** + * Forward-process given node and all of its direct and indirect children. + *

+ * Via this method, other processors can request a forward processing of some subtree. Does nothing if the given + * node was processed already, either as part of a forward reference or during normal processing. + * + * @return true iff the forward processing is legal, false otherwise. + */ + boolean processSubtree_forwardReference(RuleEnvironment G, TypableElement node, ASTMetaInfoCache cache) { + assertTrueIfRigid(cache, "argument 'node' must be an AST node", isASTNode(node)); + + // is node a valid target for a forward reference (i.e. an identifiable subtree)? + boolean valid = isIdentifiableSubtree(node) || isExceptionCaseOfForwardReferencableSubtree(node); + if (!valid) { + XtextResource resource = (XtextResource) node.eResource(); + if (resource != null) { + assertTrueIfRigid(cache, + "forward reference only allowed to identifiable subtrees; but was: " + node + " in\n" + + resource.getParseResult().getRootNode().getText(), + valid); + } else { + assertTrueIfRigid(cache, "forward reference only allowed to identifiable subtrees; but was: " + node, + valid); + } + } + + TypeRef fromCache = cache.getTypeFailSafe(node); + if (fromCache != null) { + // already processed, nothing else to do + // note: this is not an error, we may have many forward references to the same identifiable subtree + return true; + } + + if (cache.astNodesCurrentlyBeingTyped.contains(node)) { + // cyclic forward reference + // legal cases of a cyclic reference + // TODO GH-1337 add support for spread operator + boolean isCyclicForwardReference = cache.astNodesCurrentlyBeingTyped.contains(node); + if (isCyclicForwardReference && (node instanceof VariableDeclaration + || node instanceof N4ClassifierDeclaration + || node instanceof N4FieldDeclaration + || (node instanceof PropertyNameValuePair + && ((PropertyNameValuePair) node).getExpression() instanceof FunctionExpression) + || node instanceof PropertyGetterDeclaration || node instanceof PropertySetterDeclaration + || (node instanceof Expression && node.eContainer() instanceof YieldExpression))) { + return true; + } + + // illegal cyclic node inference + String msg = "*#*#*#*#*#* illegal cyclic forward reference to " + getObjectInfo(node) + + " (resource: " + (node.eResource() == null ? "null" : node.eResource().getURI()) + ")"; + logErr(msg); + return false; + } else if (isSemiCyclicForwardReferenceInForLoop(node, cache)) { + // semi-cyclic forward reference + // (this is deemed legal for the same reason why 'var x = 1+x;' is treated as a legal forward reference) + return true; + } + + if (cache.forwardProcessedSubTrees.contains(node)) { + // we saw above that the cache does not contain anything for node, so this is an error + throw new IllegalStateException(); + } + + // actually perform the forward processing + log(0, "==START of identifiable sub-tree below " + getObjectInfo(node)); + RuleEnvironment G_fresh = newRuleEnvironment(G); // use a new, empty environment here (but retain + // cancelIndicator!) + processSubtree(G_fresh, node, cache, 0); // note how we reset the indent level + cache.forwardProcessedSubTrees.add(node); + log(0, "==END of identifiable sub-tree below " + getObjectInfo(node)); + + return true; + } + + // --------------------------------------------------------------------------------------------------------------- + + /** + * Top-down processing of AST nodes happens here, i.e. this method will see all AST nodes in a top-down order. + */ + private void processNode_preChildren(RuleEnvironment G, EObject node, ASTMetaInfoCache cache) { + typeRefProcessor.handleTypeRefs(G, node, cache); + + if (node instanceof FunctionDefinition) { + handleAsyncOrGeneratorFunctionDefinition(G, (FunctionDefinition) node); + } + + typeDeferredProcessor.handleDeferredTypeRefs_preChildren(G, node, cache); + } + + /** + * Bottom-up processing of AST nodes happens here, i.e. this method will see all AST nodes in a bottom-up order. + */ + private void processNode_postChildren(RuleEnvironment G, EObject node, ASTMetaInfoCache cache, int indentLevel) { + + typeDeferredProcessor.handleDeferredTypeRefs_postChildren(G, node, cache); + + typeProcessor.typeNode(G, node, cache, indentLevel); + /* + * references to other files via import statements NOTE: for all imports except bare imports, the following is + * unnecessary, because post-processing of the target resource would be triggered automatically as soon as type + * inference is performed on an element imported from the target resource. However, by doing this eagerly up + * front, the overall flow of post-processing across multiple resources is a bit easier to understand/predict. + * This does not lead to any additional processing being done (it's just done a bit earlier), except in case of + * unused imports. + */ + if (node instanceof ImportDeclaration) { + TModule targetModule = ((ImportDeclaration) node).getModule(); + if (targetModule != null && !targetModule.eIsProxy()) { + Resource targetResource = targetModule.eResource(); + if (targetResource instanceof N4JSResource) { + // trigger post-processing of target resource + ((N4JSResource) targetResource).performPostProcessing(getCancelIndicator(G)); + } + } + } + + if (node instanceof Annotation) { + if (Objects.equals(((Annotation) node).getName(), AnnotationDefinition.STATIC_POLYFILL_AWARE.name)) { + N4JSResource resSPoly = staticPolyfillHelper.getStaticPolyfillResource(node.eResource()); + if (resSPoly != null) { + // trigger post-processing of poly filler + resSPoly.performPostProcessing(getCancelIndicator(G)); + } + } + } + + runtimeDependencyProcessor.recordRuntimeReferencesInCache(node, cache); + } + + // --------------------------------------------------------------------------------------------------------------- + + /** + * This method returns the direct children of 'obj' that are to be processed, in the order in which they are to + * be processed. By default, all direct children must be processed and the order is insignificant, so in the + * default case this method simply returns {@link EObject#eContents()}. However, this method implements special + * handling for some exception cases where the processing order is significant. + */ + private List childrenToBeProcessed(EObject obj) { + if (obj instanceof SetterDeclaration) { + // process formal parameter before body + return bringToFront(obj.eContents(), ((SetterDeclaration) obj).getFpar()); + } + if (obj instanceof FunctionDefinition) { + // process formal parameters before body + return bringToFront(obj.eContents(), ((FunctionDefinition) obj).getFpars().toArray(new EObject[0])); + } + if (obj instanceof CatchBlock) { + // process catch variable before block + return bringToFront(obj.eContents(), ((CatchBlock) obj).getCatchVariable()); + } + if (obj instanceof ForStatement) { + // process expression before varDeclOrBindings + return bringToFront(obj.eContents(), ((ForStatement) obj).getExpression()); + } + // standard case: order is insignificant (so we simply use the order provided by EMF) + return obj.eContents(); + } + + // --------------------------------------------------------------------------------------------------------------- + + /** + * Normally, forward references are allowed only to {@link N4JSLanguageUtils#isIdentifiableSubtree(EObject) + * identifiable subtrees}. However, there are exception cases that are also allowed and this method returns + * true for those cases. + */ + private static boolean isExceptionCaseOfForwardReferencableSubtree(EObject astNode) { + return isExpressionInForOf(astNode); + } + + private static boolean isExpressionInForOf(EObject astNode) { + return astNode instanceof Expression && astNode.eContainer() instanceof ForStatement + && ((ForStatement) astNode.eContainer()).isForOf() + && astNode.eContainingFeature() == N4JSPackage.eINSTANCE.getIterationStatement_Expression(); + } + + /** + * Returns true if we have a semi-cyclic reference to a variable declaration in a for in/of loop. For example: + * + *

+	 * for(var x of foo(x)) {}
+	 * 
+ */ + boolean isSemiCyclicForwardReferenceInForLoop(EObject node, ASTMetaInfoCache cache) { + if (node instanceof VariableDeclaration) { + EObject parent = node.eContainer(); + if (parent instanceof ForStatement) { + ForStatement fs = (ForStatement) parent; + return (fs.isForIn() || fs.isForOf()) && cache.astNodesCurrentlyBeingTyped.contains(fs.getExpression()); + } + } + return false; + } + + private void resolveAndProcessReferencesInNode(EObject astNode, ASTMetaInfoCache cache) { + for (EReference eRef : astNode.eClass().getEAllReferences()) { + if (!eRef.isContainment() && !eRef.isContainer()) { // only cross-references have proxies (in our case) + Object node = astNode.eGet(eRef, true); + + if (node instanceof EObject) { + recordReferencesToLocalVariables(eRef, astNode, (EObject) node, cache); + } + } + } + } + + private void recordReferencesToLocalVariables(EReference reference, EObject sourceNode, EObject target, + ASTMetaInfoCache cache) { + // skip reference Variable#definedVariable (it does not constitute a usage of the variable) + if (reference == N4JSPackage.Literals.ABSTRACT_VARIABLE__DEFINED_VARIABLE) { + return; + } + // If target is still a proxy its resolution failed, therefore it should be skipped. + if (target.eIsProxy()) { + return; + } + // skip non-local references + if (sourceNode.eResource() != target.eResource()) { + return; + } + if (target instanceof TVariable) { + // don't record references to directly exported variables + if (((TVariable) target).isDirectlyExported()) { + return; + } + + cache.storeLocalVariableReference((TVariable) target, sourceNode); + } + } + + private List bringToFront(List l, EObject... elements) { + List result = new ArrayList<>(l); + List elemSanitized = toList(filterNull(Arrays.asList(elements))); + result.removeAll(elemSanitized); + result.addAll(0, elemSanitized); + return result; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ASTProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ASTProcessor.xtend deleted file mode 100644 index e214de2616..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ASTProcessor.xtend +++ /dev/null @@ -1,540 +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 java.util.ArrayList -import java.util.List -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.EReference -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.n4JS.Annotation -import org.eclipse.n4js.n4JS.CatchBlock -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.ForStatement -import org.eclipse.n4js.n4JS.FormalParameter -import org.eclipse.n4js.n4JS.FunctionDefinition -import org.eclipse.n4js.n4JS.FunctionExpression -import org.eclipse.n4js.n4JS.IdentifierRef -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName -import org.eclipse.n4js.n4JS.N4ClassifierDeclaration -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.N4JSASTUtils -import org.eclipse.n4js.n4JS.N4JSPackage -import org.eclipse.n4js.n4JS.PropertyGetterDeclaration -import org.eclipse.n4js.n4JS.PropertyMethodDeclaration -import org.eclipse.n4js.n4JS.PropertyNameValuePair -import org.eclipse.n4js.n4JS.PropertySetterDeclaration -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.n4JS.SetterDeclaration -import org.eclipse.n4js.n4JS.ThisLiteral -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.n4JS.YieldExpression -import org.eclipse.n4js.resource.N4JSResource -import org.eclipse.n4js.ts.types.SyntaxRelatedTElement -import org.eclipse.n4js.ts.types.TVariable -import org.eclipse.n4js.ts.types.TypableElement -import org.eclipse.n4js.ts.types.TypesPackage -import org.eclipse.n4js.typesystem.utils.RuleEnvironment -import org.eclipse.n4js.utils.EcoreUtilN4 -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.StaticPolyfillHelper -import org.eclipse.xtext.resource.XtextResource -import org.eclipse.xtext.util.CancelIndicator - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* -import static extension org.eclipse.n4js.utils.N4JSLanguageUtils.* - -/** - * Main processor used during {@link N4JSPostProcessor post-processing} of N4JS resources. It controls the overall - * work flow of processing the AST, but does not do any actual work; instead, it delegates to the other processors: - *
    - *
  • {@link TypeProcessor}, which delegates further to - *
      - *
    • {@link PolyProcessor}, which delegates further to - *
        - *
      • {@link PolyProcessor_ArrayLiteral} - *
      • {@link PolyProcessor_ObjectLiteral} - *
      • {@link PolyProcessor_FunctionExpression} - *
      • {@link PolyProcessor_CallExpression} - *
      - *
    • {@link DestructureProcessor} - *
    - *
  • {@code TypeExpectedProcessor} (coming soon!) - *
  • {@link TypeDeferredProcessor} - *
- */ -@Singleton -public class ASTProcessor extends AbstractProcessor { - - @Inject - private ComputedNameProcessor computedNameProcessor; - @Inject - private TypeProcessor typeProcessor; - @Inject - private TypeRefProcessor typeRefProcessor; - @Inject - private TypeDeferredProcessor typeDeferredProcessor; - @Inject - private CompileTimeExpressionProcessor compileTimeExpressionProcessor; - @Inject - private RuntimeDependencyProcessor runtimeDependencyProcessor; - @Inject - private StaticPolyfillHelper staticPolyfillHelper - - /** - * Entry point for processing of the entire AST of the given resource. - * Will throw IllegalStateException if called more than once per N4JSResource. - *

- * This method performs some preparatory tasks (e.g., creating an instance of {@link ASTMetaInfoCache}) and ensures - * consistency by tracking the 'isProcessing' state with try/finally; for actual processing, this method delegates - * to method {@link #processAST(RuleEnvironment, Script, ASTMetaInfoCache)}. - * - * @param resource may not be null. - * @param cancelIndicator may be null. - */ - def public void processAST(N4JSResource resource, CancelIndicator cancelIndicator) { - if (resource === null) - throw new IllegalArgumentException("resource may not be null"); - - // the following is required, because typing may have been initiated by resolution of a proxy - // -> when traversing the AST, we will sooner or later try to resolve this same proxy, which would be - // interpreted as a cyclic proxy resolution by method LazyLinkingResource#getEObject(String,Triple) - resource.clearResolving(); - - log(0, "### processing resource: " + resource.URI); - - val script = resource.script; - val cache = resource.getASTMetaInfoCacheVerifyContext(); // we're during post-processing, so cache should be available now - val G = resource.newRuleEnvironment; - G.addCancelIndicator(cancelIndicator); - try { - processAST(G, script, cache); - } finally { - if (G.canceled) { - log(0, "CANCELED by cancelIndicator"); - } - - if (isDEBUG_LOG_RESULT) { - log(0, "### result for " + resource.URI); - log(4, resource.script, cache); - } - log(0, "### done: " + resource.URI); - } - } - - /** - * First method to actually perform processing of the AST. This method defines the various processing phases. - *

- * There exists a single "main phase" where 95% of processing happens (entry point for this main phase is method - * {@link #processSubtree(RuleEnvironment, EObject, ASTMetaInfoCache, int)}), plus a number of smaller phases before - * and after that where some special handling is performed. - * - * @param resource may not be null. - * @param cancelIndicator may be null. - */ - def private void processAST(RuleEnvironment G, Script script, ASTMetaInfoCache cache) { - // phase 0: process compile-time expressions & computed property names (order is important) - for(node : script.eAllContents.filter(Expression).toIterable) { - compileTimeExpressionProcessor.evaluateCompileTimeExpression(G, node, cache, 0); - } - for(node : script.eAllContents.filter(LiteralOrComputedPropertyName).toIterable) { - computedNameProcessor.processComputedPropertyName(G, node, cache, 0); - } - - // phase 1: main processing - processSubtree(G, script, cache, 0); - // phase 2: processing of postponed subtrees - var EObject eObj; - while ((eObj = cache.postponedSubTrees.poll) !== null) { - // note: we need to allow adding more postponed subtrees inside this loop! - processSubtree(G, eObj, cache, 0); - } - // phase 3: store runtime and load-time dependencies in TModule - runtimeDependencyProcessor.storeDirectRuntimeDependenciesInTModule(script, cache); - } - - /** - * Process given node and all of its direct and indirect children. - * - * @param node the root of the subtree to process; must be an AST node. - */ - def package void processSubtree(RuleEnvironment G, EObject node, ASTMetaInfoCache cache, int indentLevel) { - assertTrueIfRigid(cache, "argument 'node' must be an AST node", node.isASTNode); - - log(indentLevel, "processing: " + node.objectInfo); - - checkCanceled(G); - - // already done as part of a forward processing? - if (cache.forwardProcessedSubTrees.contains(node)) { - if (isDEBUG_LOG) { - log(indentLevel, "(subtree already processed as a forward reference)"); - if(node instanceof TypableElement) { - log(indentLevel, cache.getTypeFailSafe(node)); - } - } - return; - } - if (cache.postponedSubTrees.contains(node)) { - // in case this happens, you can either: - // * not postpone this node, or - // * handle the postponed node later (not as part of a forward reference) - throw new IllegalStateException("eager processing of postponed subtree"); - } - - if (!cache.astNodesCurrentlyBeingTyped.add(node)) { - // this subtree is currently being processed - // (can happen, for example, if we are processing a member (e.g. field) and during that processing we - // encounter a reference to the containing class (e.g. in the initializer expression)) - if (isDEBUG_LOG) { - log(indentLevel, "(subtree currently in progress - skipping)"); - } - return; - } - - try { - // process node itself - part 1 (before child processing) - processNode_preChildren(G, node, cache, indentLevel); - - // process the children - val children = childrenToBeProcessed(G, node); - for (child : children) { - if (isPostponedNode(child)) { - // postpone - cache.postponedSubTrees.add(child); - } else { - // process now - processSubtree(G, child, cache, indentLevel + 1); - checkCanceled(G); - } - } - - // process node itself - part 2 (after child processing) - processNode_postChildren(G, node, cache, indentLevel); - - // we're done with this node, but make sure that all proxies have actually been resolved - // (this is important mainly for two reasons: (1) post-processing is often triggered by a call to - // N4JSResource#resolveLazyCrossReferences(CancelIndicator), so we have to guarantee that all lazy - // cross-references are actually resolved; (2) the type system may not resolve all proxies and some - // nodes are not typed at all (i.e. isTypableNode() returns false), so we have to enforce this here. - - // We also perform all processing, related to outgoing references from the current node at this point. - resolveAndProcessReferencesInNode(node, cache); - - } finally { - cache.astNodesCurrentlyBeingTyped.remove(node); - } - } - - def private boolean isPostponedNode(EObject node) { - return isPostponedInitializer(node) - || N4JSASTUtils.isBodyOfFunctionOrFieldAccessor(node); - } - - /** - * Initializers are postponed iff: - *

    - *
  • Node is an initializer of a FormalParameter p,
  • - *
  • and p is part of a Poly FunctionExpression f,
  • - *
  • and p contains references to other FormalParameters of f, or f itself.
  • - *
- */ - def private boolean isPostponedInitializer(EObject node) { - var boolean isPostponedInitializer = false; - val fpar = node.eContainer; - if (fpar instanceof FormalParameter) { - if (node instanceof Expression) { - if (fpar.hasInitializerAssignment) { - val funDef = fpar.eContainer; - // IdentifierRef in Initializers can cause cyclic dependencies - if (funDef instanceof FunctionExpression) { - // Check if the initializer refers to other fpars - val allFPars = funDef.fpars; - val allRefs = EcoreUtilN4.getAllContentsOfTypeStopAt(fpar, IdentifierRef, N4JSPackage.Literals.FUNCTION_OR_FIELD_ACCESSOR__BODY); - - for (IdentifierRef ir : allRefs) { - var Object id = ir.getId(); - if (id instanceof SyntaxRelatedTElement) { - id = id.eGet(TypesPackage.eINSTANCE.syntaxRelatedTElement_AstElement, false); - } - val idRefCausesCyclDep = - allFPars.contains(id) // f(p, q=p) {} - || id instanceof VariableDeclaration && (id as VariableDeclaration).expression === funDef; // f(p, q=f(1)) {} - if (idRefCausesCyclDep) { - isPostponedInitializer = true; - } - } - } - // In ObjectLiterals, the ThisLiteral in Initializers can cause cyclic dependencies - // TODO GH-1337 add support for spread operator - val thisLiteralCausesCyclDep = - funDef instanceof PropertyMethodDeclaration // let o = { a:1, f(p=this.a) {} } - || funDef instanceof FunctionExpression && funDef.eContainer instanceof PropertyNameValuePair; // let o = {a:2, f: function(p=this.a) {}} - if (thisLiteralCausesCyclDep) { - val containsThisLiteral = EcoreUtilN4.containsContentsOfTypeStopAt(fpar, ThisLiteral, N4JSPackage.Literals.FUNCTION_OR_FIELD_ACCESSOR__BODY); - if (containsThisLiteral) { - isPostponedInitializer = true; - } - } - // If this check is not sufficient, we have to add more checks here. Note: Setters never have initializers. - } - } - } - return isPostponedInitializer; - } - - /** - * Forward-process given node and all of its direct and indirect children. - *

- * Via this method, other processors can request a forward processing of some subtree. Does nothing if the given - * node was processed already, either as part of a forward reference or during normal processing. - * - * @return true iff the forward processing is legal, false otherwise. - */ - def package boolean processSubtree_forwardReference(RuleEnvironment G, TypableElement node, ASTMetaInfoCache cache) { - assertTrueIfRigid(cache, "argument 'node' must be an AST node", node.isASTNode); - - // is node a valid target for a forward reference (i.e. an identifiable subtree)? - val valid = node.isIdentifiableSubtree || node.isExceptionCaseOfForwardReferencableSubtree; - if (!valid) { - val resource = node.eResource as XtextResource - if (resource !== null) { - assertTrueIfRigid(cache, - "forward reference only allowed to identifiable subtrees; but was: " + node + " in\n" + - resource.parseResult.rootNode.text, valid) - } else { - assertTrueIfRigid(cache, "forward reference only allowed to identifiable subtrees; but was: " + node, valid); - } - } - - val fromCache = cache.getTypeFailSafe(node); - if (fromCache !== null) { - // already processed, nothing else to do - // note: this is not an error, we may have many forward references to the same identifiable subtree - return true; - } - - if (cache.astNodesCurrentlyBeingTyped.contains(node)) { - // cyclic forward reference - // legal cases of a cyclic reference - // TODO GH-1337 add support for spread operator - val isCyclicForwardReference = cache.astNodesCurrentlyBeingTyped.contains(node); - if (isCyclicForwardReference && ( - node instanceof VariableDeclaration - || node instanceof N4ClassifierDeclaration - || node instanceof N4FieldDeclaration - || (node instanceof PropertyNameValuePair && (node as PropertyNameValuePair).expression instanceof FunctionExpression) - || node instanceof PropertyGetterDeclaration || node instanceof PropertySetterDeclaration - || (node instanceof Expression && node.eContainer instanceof YieldExpression) - )) { - return true; - } - - // illegal cyclic node inference - val msg = "*#*#*#*#*#* illegal cyclic forward reference to " + node.objectInfo + " (resource: " - + node.eResource?.URI + ")"; - logErr(msg); - return false; - } else if (isSemiCyclicForwardReferenceInForLoop(node, cache)) { - // semi-cyclic forward reference - // (this is deemed legal for the same reason why 'var x = 1+x;' is treated as a legal forward reference) - return true; - } - - if (cache.forwardProcessedSubTrees.contains(node)) { - // we saw above that the cache does not contain anything for node, so this is an error - throw new IllegalStateException - } - - // actually perform the forward processing - log(0, "===START of identifiable sub-tree below " + node.objectInfo); - val G_fresh = G.newRuleEnvironment; // use a new, empty environment here (but retain cancelIndicator!) - processSubtree(G_fresh, node, cache, 0); // note how we reset the indent level - cache.forwardProcessedSubTrees.add(node); - log(0, "===END of identifiable sub-tree below " + node.objectInfo); - - return true; - } - - - // --------------------------------------------------------------------------------------------------------------- - - - /** - * Top-down processing of AST nodes happens here, i.e. this method will see all AST nodes in a top-down order. - */ - def private void processNode_preChildren(RuleEnvironment G, EObject node, ASTMetaInfoCache cache, int indentLevel) { - - typeRefProcessor.handleTypeRefs(G, node, cache); - - if (node instanceof FunctionDefinition) { - handleAsyncOrGeneratorFunctionDefinition(G, node, cache); - } - - typeDeferredProcessor.handleDeferredTypeRefs_preChildren(G, node, cache); - } - - /** - * Bottom-up processing of AST nodes happens here, i.e. this method will see all AST nodes in a bottom-up order. - */ - def private void processNode_postChildren(RuleEnvironment G, EObject node, ASTMetaInfoCache cache, int indentLevel) { - - typeDeferredProcessor.handleDeferredTypeRefs_postChildren(G, node, cache); - - typeProcessor.typeNode(G, node, cache, indentLevel); - - // references to other files via import statements - // NOTE: for all imports except bare imports, the following is unnecessary, because post-processing of the target - // resource would be triggered automatically as soon as type inference is performed on an element imported from the - // target resource. However, by doing this eagerly up front, the overall flow of post-processing across multiple - // resources is a bit easier to understand/predict. This does not lead to any additional processing being done (it's - // just done a bit earlier), except in case of unused imports. - if (node instanceof ImportDeclaration) { - val targetModule = node.module; - if (targetModule !== null && !targetModule.eIsProxy) { - val targetResource = targetModule.eResource; - if (targetResource instanceof N4JSResource) { - // trigger post-processing of target resource - targetResource.performPostProcessing(G.cancelIndicator); - } - } - } - - if (node instanceof Annotation) { - if (node.name == AnnotationDefinition.STATIC_POLYFILL_AWARE.name) { - val resSPoly = staticPolyfillHelper.getStaticPolyfillResource(node.eResource); - if (resSPoly !== null) { - // trigger post-processing of poly filler - resSPoly.performPostProcessing(G.cancelIndicator); - } - } - } - - runtimeDependencyProcessor.recordRuntimeReferencesInCache(node, cache); - } - - - // --------------------------------------------------------------------------------------------------------------- - - - /** - * This method returns the direct children of 'obj' that are to be processed, in the order in which they are to - * be processed. By default, all direct children must be processed and the order is insignificant, so in the - * default case this method simply returns {@link EObject#eContents()}. However, this method implements special - * handling for some exception cases where the processing order is significant. - */ - def private List childrenToBeProcessed(RuleEnvironment G, EObject obj) { - return switch (obj) { - SetterDeclaration: { - // process formal parameter before body - obj.eContents.bringToFront(obj.fpar) - } - FunctionDefinition: { - // process formal parameters before body - obj.eContents.bringToFront(obj.fpars) - } - CatchBlock: { - // process catch variable before block - obj.eContents.bringToFront(obj.catchVariable) - } - ForStatement: { - // process expression before varDeclOrBindings - obj.eContents.bringToFront(obj.expression) - } - default: { - // standard case: order is insignificant (so we simply use the order provided by EMF) - obj.eContents - } - }; - } - - - // --------------------------------------------------------------------------------------------------------------- - - - /** - * Normally, forward references are allowed only to {@link N4JSLanguageUtils#isIdentifiableSubtree(EObject) - * identifiable subtrees}. However, there are exception cases that are also allowed and this method returns - * true for those cases. - */ - def private static boolean isExceptionCaseOfForwardReferencableSubtree(EObject astNode) { - isExpressionInForOf(astNode) - } - def private static boolean isExpressionInForOf(EObject astNode) { - astNode instanceof Expression && astNode.eContainer instanceof ForStatement - && (astNode.eContainer as ForStatement).isForOf - && astNode.eContainingFeature===N4JSPackage.eINSTANCE.iterationStatement_Expression; - } - - /** - * Returns true if we have a semi-cyclic reference to a variable declaration in a for in/of loop. - * For example: - *

-	 * for(var x of foo(x)) {}
-	 * 
- */ - def package boolean isSemiCyclicForwardReferenceInForLoop(EObject node, ASTMetaInfoCache cache) { - if (node instanceof VariableDeclaration) { - val parent = node.eContainer; - if (parent instanceof ForStatement) { - return (parent.forIn || parent.forOf) && cache.astNodesCurrentlyBeingTyped.contains(parent.expression); - } - } - return false; - } - - def private void resolveAndProcessReferencesInNode(EObject astNode, ASTMetaInfoCache cache) { - for(eRef : astNode.eClass.EAllReferences) { - if(!eRef.isContainment && !eRef.isContainer) { // only cross-references have proxies (in our case) - val node = astNode.eGet(eRef, true); - - if (node instanceof EObject) { - recordReferencesToLocalVariables(eRef, astNode, node, cache); - } - } - } - } - - def private recordReferencesToLocalVariables(EReference reference, EObject sourceNode, EObject target, ASTMetaInfoCache cache) { - - // skip reference Variable#definedVariable (it does not constitute a usage of the variable) - if (reference === N4JSPackage.Literals.ABSTRACT_VARIABLE__DEFINED_VARIABLE) { - return; - } - // If target is still a proxy its resolution failed, therefore it should be skipped. - if (target.eIsProxy) { - return; - } - // skip non-local references - if (sourceNode.eResource !== target.eResource) { - return; - } - if (target instanceof TVariable) { - // don't record references to directly exported variables - if (target.directlyExported) { - return; - } - - cache.storeLocalVariableReference(target, sourceNode); - } - } - - def private List bringToFront(List l, T... elements) { - val result = new ArrayList(l); - val elemSanitized = elements.filterNull.toList; - result.removeAll(elemSanitized); - result.addAll(0, elemSanitized); - return result; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractPolyProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractPolyProcessor.java new file mode 100644 index 0000000000..92a9142382 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractPolyProcessor.java @@ -0,0 +1,336 @@ +/** + * 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.newRuleEnvironment; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.wrap; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toSet; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.exists; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +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.ArrowFunction; +import org.eclipse.n4js.n4JS.ConditionalExpression; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.FunctionDefinition; +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.PropertyAssignmentAnnotationList; +import org.eclipse.n4js.n4JS.PropertyGetterDeclaration; +import org.eclipse.n4js.n4JS.PropertyMethodDeclaration; +import org.eclipse.n4js.n4JS.PropertyNameValuePair; +import org.eclipse.n4js.n4JS.PropertySetterDeclaration; +import org.eclipse.n4js.n4JS.PropertySpread; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExprOrRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.InferenceVariable; +import org.eclipse.n4js.ts.types.TField; +import org.eclipse.n4js.ts.types.TGetter; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.ts.types.TSetter; +import org.eclipse.n4js.ts.types.TypeVariable; +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.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper.Callable; +import org.eclipse.n4js.utils.N4JSLanguageUtils; + +import com.google.inject.Inject; + +/** + * Base for all poly processors. Contains some utility and convenience methods. + */ +abstract class AbstractPolyProcessor extends AbstractProcessor { + + @Inject + private N4JSTypeSystem ts; + @Inject + private TypeSystemHelper tsh; + + /** + * Convenience method for {@link #isPoly(Expression)} and {@link #isPoly(PropertyAssignment)}, accepting any type of + * EObject. + */ + boolean isPoly(EObject obj) { + if (obj instanceof Expression) { + return isPoly((Expression) obj); + } + if (obj instanceof PropertyAssignment) { + return isPoly((PropertyAssignment) obj); + } + return false; + } + + /** + * Tells whether the given expression is a poly expression, i.e. requires constraint-based type inference. + */ + boolean isPoly(Expression obj) { + if (obj instanceof ParameterizedCallExpression) { + ParameterizedCallExpression pce = (ParameterizedCallExpression) obj; + // NOTE: in next line, we do not propagate the cancel indicator; however, this is not required, because + // all we do with the newly created rule environment is to type a backward(!) reference, so we can be + // sure that no significant processing will be triggered by the type judgment invocation below + RuleEnvironment G = newRuleEnvironment(pce); + TypeRef targetTypeRef = ts.type(G, pce.getTarget()); // this is a backward reference (because we type obj's + // child) + Callable callable = tsh.getCallableTypeRef(G, targetTypeRef); + if (callable != null && callable.getSignatureTypeRef().isPresent()) { + FunctionTypeExprOrRef signatureTypeRef = callable.getSignatureTypeRef().get(); + return N4JSLanguageUtils.isPoly(signatureTypeRef, pce); + } else { + return false; + } + } + if (obj instanceof FunctionExpression) { + FunctionExpression fe = (FunctionExpression) obj; + return exists(fe.getFpars(), fpar -> fpar.getDeclaredTypeRefInAST() == null) + // type of 1 or more fpars is undeclared + || fe.getDeclaredReturnTypeRefInAST() == null; // return type is undeclared + // note: if the FunctionExpression is generic, this does *not* make it poly! + } + if (obj instanceof ArrayLiteral) { + return true; + } + if (obj instanceof ObjectLiteral) { + return exists(((ObjectLiteral) obj).getPropertyAssignments(), pa -> isPoly(pa)); + } + if (obj instanceof ConditionalExpression) { + ConditionalExpression ce = (ConditionalExpression) obj; + boolean trueIsPoly = isPoly(ce.getTrueExpression()); + boolean trueAllowsPoly = allowsPoly(ce.getTrueExpression()); + boolean falseAllowsPoly = allowsPoly(ce.getFalseExpression()); + boolean falseIsPoly = isPoly(ce.getFalseExpression()); + + return (trueIsPoly && falseAllowsPoly) || (falseIsPoly && trueAllowsPoly); + } + + return false; + + } + + boolean allowsPoly(Expression obj) { + if (obj == null) { + return false; + } + RuleEnvironment G = RuleEnvironmentExtensions.newRuleEnvironment(obj); + return N4JSLanguageUtils.isUndefinedLiteral(G, obj) || N4JSLanguageUtils.isNullLiteral(G, obj); + } + + /** + * True iff the given PropertyAssignment is a poly "expression", i.e. requires constraint-based type inference. + */ + private boolean isPoly(PropertyAssignment pa) { + if (pa instanceof PropertyNameValuePair) { + PropertyNameValuePair pnvp = (PropertyNameValuePair) pa; + // FIXME requiring pa.expression!=null is inconsistent! + return pnvp.getExpression() != null && pnvp.getDeclaredTypeRefInAST() == null; + } + if (pa instanceof PropertyGetterDeclaration) { + PropertyGetterDeclaration pgd = (PropertyGetterDeclaration) pa; + return pgd.getDeclaredTypeRefInAST() == null; + } + if (pa instanceof PropertySetterDeclaration) { + PropertySetterDeclaration psd = (PropertySetterDeclaration) pa; + return psd.getDeclaredTypeRefInAST() == null; + } + if (pa instanceof PropertyMethodDeclaration) { + return false; + } + if (pa instanceof PropertySpread) { + return false; // TODO GH-1337 add support for spread operator + } + if (pa instanceof PropertyAssignmentAnnotationList) { + return false; + } + + throw new IllegalArgumentException("unsupported subclass of PropertyAssignment: " + pa.eClass().getName()); + } + + /** + * Convenience method for {@link #isRootPoly(Expression)}, accepting any type of EObject. + */ + boolean isRootPoly(EObject obj) { + return (obj instanceof Expression) ? isRootPoly((Expression) obj) : false; + } + + /** + * Tells whether the given expression is a root poly expression, i.e. it + *
    + *
  1. is a {@link #isPoly(Expression) poly expression}, and + *
  2. represents the root of a tree of nested poly expressions which have to be inferred together within a single + * constraint system (this tree may have depth 0, i.e. consist only of the given expression). + *
+ */ + boolean isRootPoly(Expression obj) { + if (isPoly(obj)) { + EObject p = getParentPolyCandidate(obj); + return p == null || !isPoly(p); + } + return false; + } + + /** + * Given a poly expression, returns the parent expression that might be the parent poly expression. If the + * given expression is not poly, the return value is undefined. + */ + private EObject getParentPolyCandidate(Expression poly) { + EObject directParent = poly == null ? null : poly.eContainer(); + EObject grandParent = directParent == null ? null : directParent.eContainer(); + + if (directParent instanceof Argument) { + if (grandParent instanceof ParameterizedCallExpression && toSet(map( + ((ParameterizedCallExpression) grandParent).getArguments(), a -> a.getExpression())) + .contains(poly)) { + // TODO what about the target expression? i.e.: || directParent.target==poly) { + return grandParent; + } + } else if (directParent instanceof FunctionExpression) { + return null; // function expressions never have nested poly expressions (expression in the body are + // detached) + } else if (directParent instanceof ArrayElement) { + if (((ArrayElement) directParent).getExpression() == poly) { + return directParent.eContainer();// return the ArrayLiteral as parent (not the ArrayElement) + } + } else if (directParent instanceof PropertyNameValuePair) { + if (((PropertyNameValuePair) directParent).getExpression() == poly) { + return directParent;// return the PropertyNameValuePair as parent (not the ObjectLiteral) + } + } else if (directParent instanceof ConditionalExpression) { + return directParent; + } else if (directParent instanceof PropertyGetterDeclaration) { + return null;// getters never have nested poly expressions + } else if (directParent instanceof PropertySetterDeclaration) { + return null; // setters never have nested poly expressions + } else if (directParent instanceof PropertySpread) { + return null;// TODO GH-1337 add support for spread operator + } + + return null; + } + + // ------------------------------------------------------------------------------------------------------------------------------ + + /** + * Returns the type of a nested poly expression. The final type is returned, i.e. not the one created when preparing + * the constraint system that may contain inference variables. + *

+ * Because final types are created and stored in the typing cache in the onSuccess/onFailure lambdas and those + * lambdas of nested poly expressions are registered before those of outer expression, we can here simply read the + * nested poly expression's type from the cache. + */ + protected TypeRef getFinalResultTypeOfNestedPolyExpression(Expression nestedPolyExpression) { + return ASTMetaInfoUtils.getTypeFailSafe(nestedPolyExpression); + } + + protected TypeRef subst(TypeRef typeRef, RuleEnvironment G, + Map substitutions) { + + return subst(typeRef, G, substitutions, false); + } + + protected TypeRef subst(TypeRef typeRef, RuleEnvironment G, + Map substitutions, boolean reverse) { + + RuleEnvironment Gx = wrap(G); + for (Entry e : substitutions.entrySet()) { + if (reverse) { + Gx.put(e.getValue(), TypeUtils.createTypeRef(e.getKey())); + } else { + Gx.put(e.getKey(), TypeUtils.createTypeRef(e.getValue())); + } + } + + TypeRef typeRefSubst = ts.substTypeVariables(Gx, typeRef); + if (typeRefSubst == null) { + throw new IllegalArgumentException("substitution failed"); + } + return typeRefSubst; + } + + protected TypeRef applySolution(TypeRef typeRef, RuleEnvironment G, Map solution) { + if (typeRef == null || solution == null || solution.isEmpty()) { + return typeRef; // note: returning 'null' if typeRef==null (broken AST, etc.) + } + RuleEnvironment Gx = wrap(G); + for (Entry e : solution.entrySet()) { + Gx.put(e.getKey(), e.getValue()); + } + TypeRef typeRefSubst = ts.substTypeVariables(Gx, typeRef); + if (typeRefSubst == null) { + throw new IllegalArgumentException("substitution failed"); + } + return typeRefSubst; + } + + protected Map createPseudoSolution(InferenceContext infCtx, + TypeRef defaultTypeRef) { + + Map pseudoSolution = new HashMap<>(); + for (InferenceVariable iv : infCtx.getInferenceVariables()) { + pseudoSolution.put(iv, defaultTypeRef); // map all inference variables to the default + } + return pseudoSolution; + } + + // FIXME move to a better place + protected boolean isReturningValue(FunctionDefinition fun) { + return (fun.getBody() != null && exists(fun.getBody().getAllReturnStatements(), s -> s.getExpression() != null)) + || ((fun instanceof ArrowFunction) ? ((ArrowFunction) fun).isSingleExprImplicitReturn() : false); + // TODO except call to void function!! + } + + protected TypeRef getTypeOfMember(TMember m) { + if (m instanceof TField) { + return ((TField) m).getTypeRef(); + } else if (m instanceof TGetter) { + return ((TGetter) m).getTypeRef(); + } else if (m instanceof TSetter) { + return ((TSetter) m).getFpar().getTypeRef(); + } else if (m instanceof TMethod) { + throw new IllegalArgumentException("this method should not be used for TMethod"); + } else { + String clsName = m == null ? null : m.eClass() == null ? null : m.eClass().getName(); + throw new IllegalArgumentException("unknown subtype of TMember: " + clsName); + } + } + + protected void setTypeOfMember(TMember m, TypeRef type) { + if (m instanceof TField) { + ((TField) m).setTypeRef(type); + } else if (m instanceof TGetter) { + ((TGetter) m).setTypeRef(type); + } else if (m instanceof TSetter) { + TSetter tst = (TSetter) m; + if (tst.getFpar() != null) { + tst.getFpar().setTypeRef(type); + } + } else if (m instanceof TMethod) { + throw new IllegalArgumentException("this method should not be used for TMethod"); + } else { + String clsName = m == null ? null : m.eClass() == null ? null : m.eClass().getName(); + throw new IllegalArgumentException("unknown subtype of TMember: " + clsName); + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractPolyProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractPolyProcessor.xtend deleted file mode 100644 index 8d3b99d5f3..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractPolyProcessor.xtend +++ /dev/null @@ -1,289 +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 java.util.Map -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.ArrowFunction -import org.eclipse.n4js.n4JS.ConditionalExpression -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.FunctionDefinition -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.PropertyAssignmentAnnotationList -import org.eclipse.n4js.n4JS.PropertyGetterDeclaration -import org.eclipse.n4js.n4JS.PropertyMethodDeclaration -import org.eclipse.n4js.n4JS.PropertyNameValuePair -import org.eclipse.n4js.n4JS.PropertySetterDeclaration -import org.eclipse.n4js.n4JS.PropertySpread -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.InferenceVariable -import org.eclipse.n4js.ts.types.TField -import org.eclipse.n4js.ts.types.TGetter -import org.eclipse.n4js.ts.types.TMember -import org.eclipse.n4js.ts.types.TMethod -import org.eclipse.n4js.ts.types.TSetter -import org.eclipse.n4js.ts.types.TypeVariable -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.utils.RuleEnvironment -import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions -import org.eclipse.n4js.typesystem.utils.TypeSystemHelper -import org.eclipse.n4js.utils.N4JSLanguageUtils - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * Base for all poly processors. Contains some utility and convenience methods. - */ -package abstract class AbstractPolyProcessor extends AbstractProcessor { - - @Inject - private N4JSTypeSystem ts; - @Inject - private TypeSystemHelper tsh; - - /** - * Convenience method for {@link #isPoly(Expression)} and {@link #isPoly(PropertyAssignment)}, accepting any type of - * EObject. - */ - def boolean isPoly(EObject obj) { - return switch (obj) { - Expression: obj.isPoly - PropertyAssignment: obj.isPoly - default: false - } - } - - /** - * Tells whether the given expression is a poly expression, i.e. requires constraint-based type inference. - */ - def boolean isPoly(Expression obj) { - return switch (obj) { - ParameterizedCallExpression: { - // NOTE: in next line, we do not propagate the cancel indicator; however, this is not required, because - // all we do with the newly created rule environment is to type a backward(!) reference, so we can be - // sure that no significant processing will be triggered by the type judgment invocation below - val G = obj.newRuleEnvironment; - val TypeRef targetTypeRef = ts.type(G, obj.target); // this is a backward reference (because we type obj's child) - val callable = tsh.getCallableTypeRef(G, targetTypeRef); - if (callable !== null && callable.signatureTypeRef.present) { - val signatureTypeRef = callable.signatureTypeRef.get(); - N4JSLanguageUtils.isPoly(signatureTypeRef, obj) - } else { - false - } - } - FunctionExpression: - obj.fpars.exists[declaredTypeRefInAST === null] // type of 1 or more fpars is undeclared - || obj.declaredReturnTypeRefInAST === null // return type is undeclared - // note: if the FunctionExpression is generic, this does *not* make it poly! - ArrayLiteral: - true - ObjectLiteral: - obj.propertyAssignments.exists[isPoly] - ConditionalExpression: { - val boolean trueIsPoly = isPoly(obj.trueExpression); - val boolean trueAllowsPoly = allowsPoly(obj.trueExpression); - val boolean falseAllowsPoly = allowsPoly(obj.falseExpression); - val boolean falseIsPoly = isPoly(obj.falseExpression); - - (trueIsPoly && falseAllowsPoly) || (falseIsPoly && trueAllowsPoly) - } - default: - false - } - } - - def boolean allowsPoly(Expression obj) { - if (obj === null) { - return false; - } - val RuleEnvironment G = RuleEnvironmentExtensions.newRuleEnvironment(obj); - return N4JSLanguageUtils.isUndefinedLiteral(G, obj) || N4JSLanguageUtils.isNullLiteral(G, obj); - } - - /** - * Tells whether the given PropertyAssignment is a poly "expression", i.e. requires constraint-based type inference. - */ - def private boolean isPoly(PropertyAssignment pa) { - switch (pa) { - PropertyNameValuePair: - pa.expression !== null && pa.declaredTypeRefInAST === null // FIXME requiring pa.expression!==null is inconsistent! - PropertyGetterDeclaration: - pa.declaredTypeRefInAST === null - PropertySetterDeclaration: - pa.declaredTypeRefInAST === null - PropertyMethodDeclaration: - false - PropertySpread: - false // TODO GH-1337 add support for spread operator - PropertyAssignmentAnnotationList: - false - default: - throw new IllegalArgumentException("unsupported subclass of PropertyAssignment: " + pa.eClass.name) - } - } - - /** - * Convenience method for {@link #isRootPoly(Expression)}, accepting any type of EObject. - */ - def boolean isRootPoly(EObject obj) { - if (obj instanceof Expression) obj.isRootPoly else false - } - - /** - * Tells whether the given expression is a root poly expression, i.e. it - *

    - *
  1. is a {@link #isPoly(Expression) poly expression}, and - *
  2. represents the root of a tree of nested poly expressions which have to be inferred together within a single - * constraint system (this tree may have depth 0, i.e. consist only of the given expression). - *
- */ - def boolean isRootPoly(Expression obj) { - if (isPoly(obj)) { - val p = getParentPolyCandidate(obj); - return p === null || !isPoly(p); - } - return false; - } - - /** - * Given a poly expression, returns the parent expression that might be the parent poly expression. - * If the given expression is not poly, the return value is undefined. - */ - def private EObject getParentPolyCandidate(Expression poly) { - val directParent = poly?.eContainer; - val grandParent = directParent?.eContainer; - return switch (directParent) { - Argument case grandParent instanceof ParameterizedCallExpression && - (grandParent as ParameterizedCallExpression).arguments.map[expression].contains(poly): // TODO what about the target expression? i.e.: || directParent.target===poly - grandParent - FunctionExpression: - null // function expressions never have nested poly expressions (expression in the body are detached) - ArrayElement case directParent.expression === poly: - directParent.eContainer as ArrayLiteral // return the ArrayLiteral as parent (not the ArrayElement) - PropertyNameValuePair case directParent.expression === poly: - directParent // return the PropertyNameValuePair as parent (not the ObjectLiteral) - ConditionalExpression: - directParent - PropertyGetterDeclaration: - null // getters never have nested poly expressions - PropertySetterDeclaration: - null // setters never have nested poly expressions - PropertySpread: - null // TODO GH-1337 add support for spread operator - } - } - - - // ------------------------------------------------------------------------------------------------------------------------------ - - - /** - * Returns the type of a nested poly expression. The final type is returned, i.e. not the one created when preparing - * the constraint system that may contain inference variables. - *

- * Because final types are created and stored in the typing cache in the onSuccess/onFailure lambdas and those - * lambdas of nested poly expressions are registered before those of outer expression, we can here simply read the - * nested poly expression's type from the cache. - */ - def protected TypeRef getFinalResultTypeOfNestedPolyExpression(Expression nestedPolyExpression) { - return ASTMetaInfoUtils.getTypeFailSafe(nestedPolyExpression); - } - - def protected TypeRef subst(TypeRef typeRef, RuleEnvironment G, - Map substitutions) { - - subst(typeRef, G, substitutions, false) - } - - def protected TypeRef subst(TypeRef typeRef, RuleEnvironment G, - Map substitutions, boolean reverse) { - - val Gx = G.wrap; - substitutions.entrySet.forEach [ e | - if (reverse) - Gx.put(e.value, TypeUtils.createTypeRef(e.key)) - else - Gx.put(e.key, TypeUtils.createTypeRef(e.value)) - ]; - val typeRefSubst = ts.substTypeVariables(Gx, typeRef); - if (typeRefSubst === null) - throw new IllegalArgumentException("substitution failed"); - return typeRefSubst; - } - - def protected TypeRef applySolution(TypeRef typeRef, RuleEnvironment G, Map solution) { - if (typeRef === null || solution === null || solution.empty) { - return typeRef; // note: returning 'null' if typeRef==null (broken AST, etc.) - } - val Gx = G.wrap; - solution.entrySet.forEach[e|Gx.put(e.key, e.value)]; - val typeRefSubst = ts.substTypeVariables(Gx, typeRef); - if (typeRefSubst === null) - throw new IllegalArgumentException("substitution failed"); - return typeRefSubst; - } - - def protected Map createPseudoSolution(InferenceContext infCtx, - TypeRef defaultTypeRef) { - - val pseudoSolution = newHashMap; - for (iv : infCtx.getInferenceVariables) { - pseudoSolution.put(iv, defaultTypeRef); // map all inference variables to the default - } - return pseudoSolution; - } - - // FIXME move to a better place - def protected boolean isReturningValue(FunctionDefinition fun) { - return (fun.body !== null && fun.body.allReturnStatements.exists[expression !== null]) || - (if (fun instanceof ArrowFunction) fun.singleExprImplicitReturn else false); // TODO except call to void function!! - } - - def protected TypeRef getTypeOfMember(TMember m) { - switch (m) { - TField: - m.typeRef - TGetter: - m.typeRef - TSetter: - m?.fpar.typeRef - TMethod: - throw new IllegalArgumentException("this method should not be used for TMethod") - default: - throw new IllegalArgumentException("unknown subtype of TMember: " + m?.eClass?.name) - } - } - - def protected void setTypeOfMember(TMember m, TypeRef type) { - switch (m) { - TField: - m.typeRef = type - TGetter: - m.typeRef = type - TSetter: - if (m.fpar !== null) m.fpar.typeRef = type - TMethod: - throw new IllegalArgumentException("this method should not be used for TMethod") - default: - throw new IllegalArgumentException("unknown subtype of TMember: " + m?.eClass?.name) - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractProcessor.java new file mode 100644 index 0000000000..b1724b5eab --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractProcessor.java @@ -0,0 +1,270 @@ +/** + * 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.getBuiltInTypeScope; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.getCancelIndicator; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.isASTNode; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.isTypableNode; + +import java.util.function.BooleanSupplier; + +import org.apache.log4j.Logger; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.n4JS.FunctionDefinition; +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.n4JS.NamedElement; +import org.eclipse.n4js.resource.N4JSResource; +import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope; +import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeArgument; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.TStructMember; +import org.eclipse.n4js.ts.types.TypableElement; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.typesystem.N4JSTypeSystem; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper; +import org.eclipse.n4js.utils.EcoreUtilN4; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.UtilN4; +import org.eclipse.xtext.nodemodel.ICompositeNode; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.service.OperationCanceledManager; +import org.eclipse.xtext.util.CancelIndicator; + +import com.google.common.base.Throwables; +import com.google.inject.Inject; + +/** + * Provides some common base functionality used across all processors (e.g. logging). See {@link ASTProcessor} for more + * details on processors and post-processing of {@link N4JSResource}s. + */ +abstract class AbstractProcessor { + + private static Logger LOG = Logger.getLogger(AbstractProcessor.class); + + private static boolean DEBUG_LOG = false; + private static boolean DEBUG_LOG_RESULT = false; + private static boolean DEBUG_RIGID = false; // if true, more consistency checks are performed and exceptions thrown + // if wrong + + @Inject + private N4JSTypeSystem ts; + @Inject + private TypeSystemHelper tsh; + @Inject + private OperationCanceledManager operationCanceledManager; + + /** + * Convenience method. Same as {@link OperationCanceledManager#checkCanceled(CancelIndicator)}, using the cancel + * indicator of the given rule environment. + */ + protected void checkCanceled(RuleEnvironment G) { + operationCanceledManager.checkCanceled(getCancelIndicator(G)); + } + + /** + * Processors can call this method to directly invoke the 'type' judgment, i.e. invoke method + * {@code TypeJudgment#apply()} via facade method + * {@link N4JSTypeSystem#use_type_judgment_from_PostProcessors(RuleEnvironment, TypableElement) + * use_type_judgment_from_PostProcessors()}. Normally, this should only be required by {@link TypeProcessor}, so use + * this sparingly (however, sometimes it can be helpful to avoid duplication of logic). + */ + protected TypeRef invokeTypeJudgmentToInferType(RuleEnvironment G, TypableElement elem) { + if (elem.eIsProxy()) { + return TypeRefsFactory.eINSTANCE.createUnknownTypeRef(); + } + // special case: + // TStructMembers are special in that they may be types (in case of TStructMethod) and appear as AST nodes + // -> if we are dealing with an AST node, make sure to use the definedMember in the TModule + TStructMember definedMember = (elem instanceof TStructMember) + ? ((TStructMember) elem).getDefinedMember() + : null; + if (definedMember != null && isASTNode(elem)) { + return invokeTypeJudgmentToInferType(G, definedMember); + } + return ts.use_type_judgment_from_PostProcessors(G, elem); + } + + /** + * Some special handling for async and/or generator functions (including methods): we have to wrap their inner + * return type R into a {@code Promise}, {@code Generator}, or + * {@code AsyncGenerator} and use that as their actual, outer return type. This means for async and/or + * generator functions, the types builder will create a TFunction with the inner return type and during + * post-processing this method will change that return type to a + * Promise/Generator/AsyncGenerator (only the return type of the TFunction in + * the types model is changed; the declared return type in the AST remains unchanged). + *

+ * In addition, a return type of void will be replaced by undefined, i.e. will produce an + * outer return type of Promise<undefined,?>, Generator<undefined,undefined,TNext>, + * etc. This will be taken care of by utility methods + * {@link TypeUtils#createPromiseTypeRef(BuiltInTypeScope,TypeArgument,TypeArgument)} and + * {@link TypeUtils#createGeneratorTypeRef(BuiltInTypeScope,FunctionDefinition)}, respectively. + *

+ * NOTES: + *

    + *
  1. normally, this wrapping could easily be done in the types builder, but because we have to check if the inner + * return type is void we need to resolve proxies, which is not allowed in the types builder. + *
+ */ + protected void handleAsyncOrGeneratorFunctionDefinition(RuleEnvironment G, FunctionDefinition funDef) { + boolean isAsync = funDef.isAsync(); + boolean isGenerator = funDef.isGenerator(); + if (isAsync || isGenerator) { + Type tFunction = funDef.getDefinedType(); + if (tFunction instanceof TFunction) { + TFunction tFun = (TFunction) tFunction; + TypeRef innerReturnTypeRef = tFun.getReturnTypeRef(); + if (innerReturnTypeRef != null && !(innerReturnTypeRef instanceof DeferredTypeRef)) { + // we took 'innerReturnTypeRef' from the TModule (not the AST), so normally we would not have to + // invoke #resolveTypeAliases() here; however, since this code is running before TypeAliasProcessor, + // we still have to invoke #resolveTypeAliases(): + TypeRef innerReturnTypeRefResolved = tsh.resolveTypeAliases(G, innerReturnTypeRef); + TypeRef innerReturnTypeRefResolvedUB = ts.upperBoundWithReopenAndResolveTypeVars(G, + innerReturnTypeRefResolved); + BuiltInTypeScope scope = getBuiltInTypeScope(G); + boolean needsRewrite = !N4JSLanguageUtils.hasExpectedSpecialReturnType(innerReturnTypeRefResolvedUB, + funDef, scope); + if (needsRewrite) { + TypeRef outerReturnTypeRef = (!isGenerator) + ? TypeUtils.createPromiseTypeRef(scope, innerReturnTypeRef, null) + : + // note: this method handles the choice Generator vs. AsyncGenerator + TypeUtils.createGeneratorTypeRef(scope, funDef); + EcoreUtilN4.doWithDeliver(false, () -> tFun.setReturnTypeRef(outerReturnTypeRef), tFun); + } + } + } + } + } + + protected static String getObjectInfo(EObject obj) { + if (obj == null) { + return ""; + } else if (obj instanceof IdentifierRef) { + ICompositeNode node = NodeModelUtils.findActualNodeFor(obj); + if (node == null) { + return ""; + } else { + return "IdentifierRef \"" + NodeModelUtils.getTokenText(node) + "\""; + } + } else { + String name = getName(obj); + if (name != null) { + return obj.eClass().getName() + " \"" + name + "\""; + } else { + return obj.eClass().getName(); + } + } + } + + protected static String getName(EObject obj) { + if (obj instanceof NamedElement) { + return ((NamedElement) obj).getName(); + } + if (obj instanceof IdentifiableElement) { + return ((IdentifiableElement) obj).getName(); + } + return null; + } + + protected static void log(int indentLevel, TypeRef result) { + if (!isDEBUG_LOG()) { + return; + } + log(indentLevel, result.getTypeRefAsString()); + } + + protected static void log(int indentLevel, EObject astNode, ASTMetaInfoCache cache) { + if (!isDEBUG_LOG()) + return; + if (isTypableNode(astNode)) { + TypeRef result = cache.getTypeFailSafe((TypableElement) astNode); + String resultStr = (result != null) ? result.getTypeRefAsString() : "*** MISSING ***"; + log(indentLevel, getObjectInfo(astNode) + " " + resultStr); + } else { + log(indentLevel, getObjectInfo(astNode)); + } + for (EObject childNode : astNode.eContents()) { + log(indentLevel + 1, childNode, cache); + } + } + + protected static void log(int indentLevel, String msg) { + if (!isDEBUG_LOG()) { + return; + } + System.out.println(indent(indentLevel) + msg); + } + + protected static void logErr(String msg) { + // always log errors, even if !isDEBUG_LOG() + System.out.flush(); + System.err.println(msg); + System.err.flush(); + LOG.error(msg); + } + + protected static Throwable logException(String msg, Throwable th) { + // always log exceptions, even if !isDEBUG_LOG() + th.printStackTrace(); // enforce dumping all exceptions to stderr + // GH-2002: TEMPORARY DEBUG LOGGING + // Only passing the exception to Logger#error(String,Throwable) does not emit the stack trace of the caught + // exception in all logger configurations; we therefore include the stack trace in the main message: + LOG.error(msg + "\n" + Throwables.getStackTraceAsString(th), th); + return th; + } + + protected static void assertTrueIfRigid(ASTMetaInfoCache cache, String message, BooleanSupplier check) { + if (isDEBUG_RIGID()) { + assertTrueIfRigid(cache, message, check.getAsBoolean()); + } + } + + protected static void assertTrueIfRigid(ASTMetaInfoCache cache, String message, boolean actual) { + if (isDEBUG_RIGID() && !actual) { + Error e = new Error(message); + if (!cache.hasBrokenAST()) { + // make sure we see this exception on the console, even if it gets caught somewhere + UtilN4.reportError(e); + } + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); + } + } + + // using a method to read field DEBUG_LOG to get rid of Xtend's "Constant condition is always true|false." warnings + protected static boolean isDEBUG_LOG() { + return DEBUG_LOG; + } + + protected static boolean isDEBUG_LOG_RESULT() { + return DEBUG_LOG_RESULT; + } + + protected static boolean isDEBUG_RIGID() { + return DEBUG_RIGID; + } + + protected static String indent(int indentLevel) { + String res = ""; + for (int i = 0; i < indentLevel; i++) { + res += " "; + } + return res; + + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractProcessor.xtend deleted file mode 100644 index 158e1d1d2a..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/AbstractProcessor.xtend +++ /dev/null @@ -1,247 +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.common.base.Throwables -import com.google.inject.Inject -import java.util.function.BooleanSupplier -import org.apache.log4j.Logger -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.n4JS.FunctionDefinition -import org.eclipse.n4js.n4JS.IdentifierRef -import org.eclipse.n4js.n4JS.NamedElement -import org.eclipse.n4js.resource.N4JSResource -import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory -import org.eclipse.n4js.ts.types.IdentifiableElement -import org.eclipse.n4js.ts.types.TFunction -import org.eclipse.n4js.ts.types.TStructMember -import org.eclipse.n4js.ts.types.TypableElement -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.typesystem.N4JSTypeSystem -import org.eclipse.n4js.typesystem.utils.RuleEnvironment -import org.eclipse.n4js.typesystem.utils.TypeSystemHelper -import org.eclipse.n4js.utils.EcoreUtilN4 -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.UtilN4 -import org.eclipse.xtext.nodemodel.util.NodeModelUtils -import org.eclipse.xtext.service.OperationCanceledManager - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* -import static extension org.eclipse.n4js.utils.N4JSLanguageUtils.* - -/** - * Provides some common base functionality used across all processors (e.g. logging). See {@link ASTProcessor} for more - * details on processors and post-processing of {@link N4JSResource}s. - */ -package abstract class AbstractProcessor { - - val private static Logger LOG = Logger.getLogger(AbstractProcessor); - - val private static DEBUG_LOG = false; - val private static DEBUG_LOG_RESULT = false; - val private static DEBUG_RIGID = false; // if true, more consistency checks are performed and exceptions thrown if wrong - - @Inject - private N4JSTypeSystem ts; - @Inject - private TypeSystemHelper tsh; - @Inject - private OperationCanceledManager operationCanceledManager; - - - /** - * Convenience method. Same as {@link OperationCanceledManager#checkCanceled(CancelIndicator)}, using the cancel - * indicator of the given rule environment. - */ - def protected void checkCanceled(RuleEnvironment G) { - operationCanceledManager.checkCanceled(G.cancelIndicator); - } - - - /** - * Processors can call this method to directly invoke the 'type' judgment, i.e. invoke method {@code TypeJudgment#apply()} - * via facade method {@link N4JSTypeSystem#use_type_judgment_from_PostProcessors(RuleEnvironment, TypableElement) - * use_type_judgment_from_PostProcessors()}. Normally, this should only be required by {@link TypeProcessor}, so use - * this sparingly (however, sometimes it can be helpful to avoid duplication of logic). - */ - def protected TypeRef invokeTypeJudgmentToInferType(RuleEnvironment G, TypableElement elem) { - if (elem.eIsProxy) { - return TypeRefsFactory.eINSTANCE.createUnknownTypeRef; - } - // special case: - // TStructMembers are special in that they may be types (in case of TStructMethod) and appear as AST nodes - // -> if we are dealing with an AST node, make sure to use the definedMember in the TModule - val definedMember = if (elem instanceof TStructMember) elem.definedMember; - if (definedMember !== null && elem.isASTNode) { - return invokeTypeJudgmentToInferType(G, definedMember); - } - return ts.use_type_judgment_from_PostProcessors(G, elem); - } - - - /** - * Some special handling for async and/or generator functions (including methods): we have to wrap their inner return type - * R into a {@code Promise}, {@code Generator}, or {@code AsyncGenerator} and use - * that as their actual, outer return type. This means for async and/or generator functions, the types builder will create - * a TFunction with the inner return type and during post-processing this method will change that return type - * to a Promise/Generator/AsyncGenerator (only the return type of the TFunction in - * the types model is changed; the declared return type in the AST remains unchanged). - *

- * In addition, a return type of void will be replaced by undefined, i.e. will produce an outer - * return type of Promise<undefined,?>, Generator<undefined,undefined,TNext>, etc. This will - * be taken care of by utility methods {@link TypeUtils#createPromiseTypeRef(BuiltInTypeScope,TypeArgument,TypeArgument)} - * and {@link TypeUtils#createGeneratorTypeRef(BuiltInTypeScope,FunctionDefinition)}, respectively. - *

- * NOTES: - *

    - *
  1. normally, this wrapping could easily be done in the types builder, but because we have to check if the inner - * return type is void we need to resolve proxies, which is not allowed in the types builder. - *
- */ - def protected void handleAsyncOrGeneratorFunctionDefinition(RuleEnvironment G, FunctionDefinition funDef, ASTMetaInfoCache cache) { - val isAsync = funDef.isAsync; - val isGenerator = funDef.isGenerator; - if(isAsync || isGenerator) { - val tFunction = funDef.definedType; - if(tFunction instanceof TFunction) { - val innerReturnTypeRef = tFunction.returnTypeRef; - if (innerReturnTypeRef !== null && !(innerReturnTypeRef instanceof DeferredTypeRef)) { - // we took 'innerReturnTypeRef' from the TModule (not the AST), so normally we would not have to - // invoke #resolveTypeAliases() here; however, since this code is running before TypeAliasProcessor, - // we still have to invoke #resolveTypeAliases(): - val innerReturnTypeRefResolved = tsh.resolveTypeAliases(G, innerReturnTypeRef); - val innerReturnTypeRefResolvedUB = ts.upperBoundWithReopenAndResolveTypeVars(G, innerReturnTypeRefResolved); - val scope = G.builtInTypeScope; - val needsRewrite = !N4JSLanguageUtils.hasExpectedSpecialReturnType(innerReturnTypeRefResolvedUB, funDef, scope); - if (needsRewrite) { - val outerReturnTypeRef = if (!isGenerator) { - TypeUtils.createPromiseTypeRef(scope, innerReturnTypeRef, null); - } else { - TypeUtils.createGeneratorTypeRef(scope, funDef); // note: this method handles the choice Generator vs. AsyncGenerator - }; - EcoreUtilN4.doWithDeliver(false, [ - tFunction.returnTypeRef = outerReturnTypeRef; - ], tFunction); - } - } - } - } - } - - def protected static String getObjectInfo(EObject obj) { - if (obj === null) { - "" - } else if (obj instanceof IdentifierRef) { - val node = NodeModelUtils.findActualNodeFor(obj); - if (node === null) { - "" - } else { - "IdentifierRef \"" + NodeModelUtils.getTokenText(node) + "\"" - } - } else { - val name = obj.name; - if (name !== null) { - obj.eClass.name + " \"" + name + "\"" - } else { - obj.eClass.name - } - } - } - - def protected static String getName(EObject obj) { - switch (obj) { - NamedElement: obj.name - IdentifiableElement: obj.name - } - } - - def protected static void log(int indentLevel, TypeRef result) { - if (!isDEBUG_LOG) - return; - log(indentLevel, result.typeRefAsString); - } - - def protected static void log(int indentLevel, EObject astNode, ASTMetaInfoCache cache) { - if (!isDEBUG_LOG) - return; - if (astNode.isTypableNode) { - val result = cache.getTypeFailSafe(astNode as TypableElement); - val resultStr = if (result !== null) result.typeRefAsString else "*** MISSING ***"; - log(indentLevel, astNode.objectInfo + " " + resultStr); - } else { - log(indentLevel, astNode.objectInfo); - } - for (childNode : astNode.eContents) { - log(indentLevel + 1, childNode, cache); - } - } - - def protected static void log(int indentLevel, String msg) { - if (!isDEBUG_LOG) - return; - println(indent(indentLevel) + msg); - } - - def protected static void logErr(String msg) { - // always log errors, even if !isDEBUG_LOG() - System.out.flush(); - System.err.println(msg); - System.err.flush(); - LOG.error(msg); - } - - def protected static Throwable logException(String msg, Throwable th) { - // always log exceptions, even if !isDEBUG_LOG() - th.printStackTrace // enforce dumping all exceptions to stderr - // GH-2002: TEMPORARY DEBUG LOGGING - // Only passing the exception to Logger#error(String,Throwable) does not emit the stack trace of the caught - // exception in all logger configurations; we therefore include the stack trace in the main message: - LOG.error(msg + "\n" + Throwables.getStackTraceAsString(th), th); - return th; - } - - def protected static void assertTrueIfRigid(ASTMetaInfoCache cache, String message, BooleanSupplier check) { - if (isDEBUG_RIGID) { - assertTrueIfRigid(cache, message, check.asBoolean); - } - } - - def protected static void assertTrueIfRigid(ASTMetaInfoCache cache, String message, boolean actual) { - if (isDEBUG_RIGID && !actual) { - val e = new Error(message); - if(!cache.hasBrokenAST) { - // make sure we see this exception on the console, even if it gets caught somewhere - UtilN4.reportError(e); - } - Throwables.throwIfUnchecked(e); - throw new RuntimeException(e); - } - } - - // using a method to read field DEBUG_LOG to get rid of Xtend's "Constant condition is always true|false." warnings - def protected static boolean isDEBUG_LOG() { - return DEBUG_LOG; - } - - def protected static boolean isDEBUG_LOG_RESULT() { - return DEBUG_LOG_RESULT; - } - - def protected static boolean isDEBUG_RIGID() { - return DEBUG_RIGID; - } - - def protected static String indent(int indentLevel) { - (0 ..< indentLevel).map[" "].join - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/CompileTimeExpressionProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/CompileTimeExpressionProcessor.java new file mode 100644 index 0000000000..8aae96c70f --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/CompileTimeExpressionProcessor.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2017 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 org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.compileTime.CompileTimeEvaluator; +import org.eclipse.n4js.compileTime.CompileTimeValue; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.ts.types.TConstableElement; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.utils.EcoreUtilN4; +import org.eclipse.n4js.utils.N4JSLanguageUtils; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * Processing of compile-time expressions. + *

+ * All expressions for which {@link N4JSLanguageUtils#isProcessedAsCompileTimeExpression(Expression)} returns + * true are evaluated and the resulting {@link CompileTimeValue} is stored in the cache. In some cases, the + * value is also stored in the TModule. + */ +@Singleton +public class CompileTimeExpressionProcessor { + + @Inject + private CompileTimeEvaluator compileTimeEvaluator; + + /** + * If the given AST node is an expression that is directly processed as a compile-time expression (cf. + * {@link N4JSLanguageUtils#isProcessedAsCompileTimeExpression(Expression)}, this method will evaluate the + * expression and store the evaluation result in the given cache; otherwise, this method will do nothing. + */ + public void evaluateCompileTimeExpression(RuleEnvironment G, Expression astNode, ASTMetaInfoCache cache) { + if (N4JSLanguageUtils.isProcessedAsCompileTimeExpression(astNode)) { + CompileTimeValue value = compileTimeEvaluator.evaluateCompileTimeExpression(G, astNode); + cache.storeCompileTimeValue(astNode, value); + + // in some cases, we have to store the compile-time value in the TModule: + EObject parent = astNode.eContainer(); + if (parent instanceof VariableDeclaration) { + VariableDeclaration vd = (VariableDeclaration) parent; + if (vd.isDirectlyExported()) { + storeValueInTModule(vd.getDefinedVariable(), value); + } + } else if (parent instanceof N4FieldDeclaration) { + N4FieldDeclaration fd = (N4FieldDeclaration) parent; + storeValueInTModule(fd.getDefinedField(), value); + } + } + } + + private void storeValueInTModule(TConstableElement elem, CompileTimeValue value) { + if (elem != null && elem.isConst()) { + String valueStr = CompileTimeValue.serialize(value); + EcoreUtilN4.doWithDeliver(false, () -> { + elem.setCompileTimeValue(valueStr); + }, elem); + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/CompileTimeExpressionProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/CompileTimeExpressionProcessor.xtend deleted file mode 100644 index 131327ad52..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/CompileTimeExpressionProcessor.xtend +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2017 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.n4js.compileTime.CompileTimeEvaluator -import org.eclipse.n4js.compileTime.CompileTimeValue -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.ts.types.TConstableElement -import org.eclipse.n4js.typesystem.utils.RuleEnvironment -import org.eclipse.n4js.utils.EcoreUtilN4 -import org.eclipse.n4js.utils.N4JSLanguageUtils - -/** - * Processing of compile-time expressions. - *

- * All expressions for which {@link N4JSLanguageUtils#isProcessedAsCompileTimeExpression(Expression)} returns - * true are evaluated and the resulting {@link CompileTimeValue} is stored in the cache. In some cases, - * the value is also stored in the TModule. - */ -@Singleton -class CompileTimeExpressionProcessor { - - @Inject - private CompileTimeEvaluator compileTimeEvaluator; - - /** - * If the given AST node is an expression that is directly processed as a compile-time expression (cf. - * {@link N4JSLanguageUtils#isProcessedAsCompileTimeExpression(Expression)}, this method will evaluate the - * expression and store the evaluation result in the given cache; otherwise, this method will do nothing. - */ - def public void evaluateCompileTimeExpression(RuleEnvironment G, Expression astNode, ASTMetaInfoCache cache, - int indentLevel) { - - if (N4JSLanguageUtils.isProcessedAsCompileTimeExpression(astNode)) { - val value = compileTimeEvaluator.evaluateCompileTimeExpression(G, astNode); - cache.storeCompileTimeValue(astNode, value); - - // in some cases, we have to store the compile-time value in the TModule: - val parent = astNode.eContainer; - if (parent instanceof VariableDeclaration) { - if (parent.directlyExported) { - storeValueInTModule(G, parent.definedVariable, value); - } - } else if (parent instanceof N4FieldDeclaration) { - storeValueInTModule(G, parent.definedField, value); - } - } - } - - def private void storeValueInTModule(RuleEnvironment G, TConstableElement elem, CompileTimeValue value) { - if (elem !== null && elem.const) { - val valueStr = CompileTimeValue.serialize(value); - EcoreUtilN4.doWithDeliver(false, [ - elem.compileTimeValue = valueStr; - ], elem); - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ComputedNameProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ComputedNameProcessor.java new file mode 100644 index 0000000000..b8429a6d55 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ComputedNameProcessor.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2017 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 org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.n4js.compileTime.CompileTimeValue; +import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4GetterDeclaration; +import org.eclipse.n4js.n4JS.N4JSASTUtils; +import org.eclipse.n4js.n4JS.N4SetterDeclaration; +import org.eclipse.n4js.n4JS.PropertyGetterDeclaration; +import org.eclipse.n4js.n4JS.PropertyNameValuePair; +import org.eclipse.n4js.n4JS.PropertySetterDeclaration; +import org.eclipse.n4js.n4JS.PropertySpread; +import org.eclipse.n4js.n4JS.TypeDefiningElement; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.SyntaxRelatedTElement; +import org.eclipse.n4js.utils.EcoreUtilN4; +import org.eclipse.n4js.utils.N4JSLanguageUtils; + +import com.google.inject.Singleton; + +/** + * Processing of {@link LiteralOrComputedPropertyName}s that have a computed property name, mainly setting property + * {@link LiteralOrComputedPropertyName#getComputedName() 'computedName'}. + *

+ * For details, see + * {@link ComputedNameProcessor#processComputedPropertyName( LiteralOrComputedPropertyName, ASTMetaInfoCache )}. + */ +@Singleton +public class ComputedNameProcessor { + + /** + * If the given 'nameDecl' has a computed property name, this method will + *

    + *
  1. obtain its expression's compile-time value from the cache (the actual evaluation of the expression happened + * in {@link CompileTimeExpressionProcessor}), + *
  2. store this name in 'nameDecl' (for later use), and + *
  3. store this name in the corresponding TModule element (if such a TModule element exists). + *
+ *

+ * In case the compile-time value of the expression is invalid (i.e. the expression is not a valid compile-time + * expression) no actual name will be stored in 'nameDecl' and the corresponding TModule element will be removed + * from the TModule, entirely (if such a TModule element exists). + */ + public void processComputedPropertyName(LiteralOrComputedPropertyName nameDecl, ASTMetaInfoCache cache) { + if (nameDecl.hasComputedPropertyName()) { + // obtain compile-time value of expression + CompileTimeValue value = cache.getCompileTimeValue(nameDecl.getExpression()); + // derive a property name from the value + String name = N4JSLanguageUtils.derivePropertyNameFromCompileTimeValue(value); + if (name != null) { + // cache the computed name in the LiteralOrComputedPropertyName AST node + EcoreUtilN4.doWithDeliver(false, () -> { + nameDecl.setComputedName(name); + nameDecl.setComputedSymbol(value instanceof CompileTimeValue.ValueSymbol); + }, nameDecl); + // set the computed name in the types model element + EObject owner = nameDecl.eContainer(); + EObject typeElem = N4JSASTUtils.getCorrespondingTypeModelElement(owner); + if (typeElem instanceof IdentifiableElement) { + EcoreUtilN4.doWithDeliver(false, () -> { + ((IdentifiableElement) typeElem).setName(name); + }, typeElem); + } + } else { + // invalid name expression (i.e. not a constant expression) + // -> remove the types model element from the TModule + // (note: we have to do this for consistency with how the types builder handles elements that are + // unnamed (usually due to a broken AST): in those cases, the types builder does not create a TModule + // element) + EObject owner = nameDecl.eContainer(); + discardTypeModelElement(owner); + } + } + } + + /** + * Discards the types model element corresponding to the given AST node. Throws exception if given AST node does not + * have a corresponding types model element. + */ + private void discardTypeModelElement(EObject astNode) { + EObject elem = N4JSASTUtils.getCorrespondingTypeModelElement(astNode); + + EcoreUtilN4.doWithDeliver(false, () -> { + if (astNode instanceof TypeDefiningElement) { + ((TypeDefiningElement) astNode).setDefinedType(null); + } else if (astNode instanceof N4FieldDeclaration) { + ((N4FieldDeclaration) astNode).setDefinedField(null); + } else if (astNode instanceof N4GetterDeclaration) { + ((N4GetterDeclaration) astNode).setDefinedGetter(null); + } else if (astNode instanceof N4SetterDeclaration) { + ((N4SetterDeclaration) astNode).setDefinedSetter(null); + } else if (astNode instanceof PropertyNameValuePair) { + ((PropertyNameValuePair) astNode).setDefinedField(null); + } else if (astNode instanceof PropertyGetterDeclaration) { + ((PropertyGetterDeclaration) astNode).setDefinedGetter(null); + } else if (astNode instanceof PropertySetterDeclaration) { + ((PropertySetterDeclaration) astNode).setDefinedSetter(null); + // note: PropertyMethodDeclaration is a TypeDefiningElement (handled above) + } else if (astNode instanceof PropertySpread) { + // nothing to discard in this case + } else { + throw new UnsupportedOperationException("switch case missing for: " + astNode); + } + }, astNode); + + if (elem instanceof SyntaxRelatedTElement) { + ((SyntaxRelatedTElement) elem).setAstElement(null); + } + if (elem != null) { + EcoreUtilN4.doWithDeliver(false, () -> { + EcoreUtil.remove(elem); + }, elem.eContainer()); + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ComputedNameProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ComputedNameProcessor.xtend deleted file mode 100644 index 4bb8100700..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ComputedNameProcessor.xtend +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Copyright (c) 2017 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.Singleton -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.compileTime.CompileTimeValue -import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.N4GetterDeclaration -import org.eclipse.n4js.n4JS.N4JSASTUtils -import org.eclipse.n4js.n4JS.N4SetterDeclaration -import org.eclipse.n4js.n4JS.PropertyGetterDeclaration -import org.eclipse.n4js.n4JS.PropertyNameValuePair -import org.eclipse.n4js.n4JS.PropertySetterDeclaration -import org.eclipse.n4js.n4JS.PropertySpread -import org.eclipse.n4js.n4JS.TypeDefiningElement -import org.eclipse.n4js.ts.types.IdentifiableElement -import org.eclipse.n4js.ts.types.SyntaxRelatedTElement -import org.eclipse.n4js.typesystem.utils.RuleEnvironment -import org.eclipse.n4js.utils.EcoreUtilN4 -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.xtext.EcoreUtil2 - -/** - * Processing of {@link LiteralOrComputedPropertyName}s that have a computed property name, mainly setting property - * {@link LiteralOrComputedPropertyName#getComputedName() 'computedName'}. - *

- * For details, see {@link ComputedNameProcessor#processComputedPropertyName(RuleEnvironment, LiteralOrComputedPropertyName, ASTMetaInfoCache, int)}. - */ -@Singleton -class ComputedNameProcessor { - - /** - * If the given 'nameDecl' has a computed property name, this method will - *

    - *
  1. obtain its expression's compile-time value from the cache (the actual evaluation of the expression happened - * in {@link CompileTimeExpressionProcessor}), - *
  2. derive the actual property name from that value (cf. {@link #getPropertyNameFromExpression(RuleEnvironment, Expression, ASTMetaInfoCache)}), - *
  3. store this name in 'nameDecl' (for later use), and - *
  4. store this name in the corresponding TModule element (if such a TModule element exists). - *
- *

- * In case the compile-time value of the expression is invalid (i.e. the expression is not a valid compile-time - * expression) no actual name will be stored in 'nameDecl' and the corresponding TModule element will be removed - * from the TModule, entirely (if such a TModule element exists). - */ - def public void processComputedPropertyName(RuleEnvironment G, LiteralOrComputedPropertyName nameDecl, - ASTMetaInfoCache cache, int indentLevel) { - - if (nameDecl.hasComputedPropertyName) { - // obtain compile-time value of expression - val value = cache.getCompileTimeValue(nameDecl.expression); - // derive a property name from the value - val name = N4JSLanguageUtils.derivePropertyNameFromCompileTimeValue(value); - if (name !== null) { - // cache the computed name in the LiteralOrComputedPropertyName AST node - EcoreUtilN4.doWithDeliver(false, [ - nameDecl.computedName = name; - nameDecl.computedSymbol = value instanceof CompileTimeValue.ValueSymbol; - ], nameDecl); - // set the computed name in the types model element - val owner = nameDecl.eContainer; - val typeElem = N4JSASTUtils.getCorrespondingTypeModelElement(owner); - if (typeElem instanceof IdentifiableElement) { - EcoreUtilN4.doWithDeliver(false, [ - typeElem.name = name; - ], typeElem); - } - } else { - // invalid name expression (i.e. not a constant expression) - // -> remove the types model element from the TModule - // (note: we have to do this for consistency with how the types builder handles elements that are - // unnamed (usually due to a broken AST): in those cases, the types builder does not create a TModule - // element) - val owner = nameDecl.eContainer; - discardTypeModelElement(owner); - } - } - } - - /** - * Discards the types model element corresponding to the given AST node. Throws exception if given AST node does not - * have a corresponding types model element. - */ - def private void discardTypeModelElement(EObject astNode) { - val elem = N4JSASTUtils.getCorrespondingTypeModelElement(astNode); - - EcoreUtilN4.doWithDeliver(false, [ - switch (astNode) { - TypeDefiningElement: - astNode.definedType = null - N4FieldDeclaration: - astNode.definedField = null - N4GetterDeclaration: - astNode.definedGetter = null - N4SetterDeclaration: - astNode.definedSetter = null - PropertyNameValuePair: - astNode.definedField = null - PropertyGetterDeclaration: - astNode.definedGetter = null - PropertySetterDeclaration: - astNode.definedSetter = null - // note: PropertyMethodDeclaration is a TypeDefiningElement (handled above) - PropertySpread: { - // nothing to discard in this case - } - default: - throw new UnsupportedOperationException("switch case missing for: " + astNode) - }; - ], astNode); - - if (elem instanceof SyntaxRelatedTElement) { - elem.astElement = null; - } - if (elem !== null) { - EcoreUtilN4.doWithDeliver(false, [ - EcoreUtil2.remove(elem); - ], elem.eContainer); - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/DestructureProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/DestructureProcessor.java new file mode 100644 index 0000000000..dd88121cc0 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/DestructureProcessor.java @@ -0,0 +1,143 @@ +/** + * 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.anyTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.undefinedTypeRef; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.toIterable; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.n4js.n4JS.ArrayElement; +import org.eclipse.n4js.n4JS.ArrayLiteral; +import org.eclipse.n4js.n4JS.AssignmentExpression; +import org.eclipse.n4js.n4JS.BindingElement; +import org.eclipse.n4js.n4JS.DestructureUtils; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.ForStatement; +import org.eclipse.n4js.n4JS.ObjectLiteral; +import org.eclipse.n4js.n4JS.PropertyAssignment; +import org.eclipse.n4js.n4JS.PropertyNameValuePair; +import org.eclipse.n4js.n4JS.VariableBinding; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.TypableElement; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions; +import org.eclipse.n4js.utils.EcoreUtilN4; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * Deals with destructuring patterns during post processing of an N4JS resource (only the destructuring pattern; the + * value to be destructured is handled normally by the other processors). + *

+ * TODO clean up handling of destructuring patterns during AST traversal, IDE-1714 + */ +@Singleton +class DestructureProcessor extends AbstractProcessor { + + @Inject + private ASTProcessor astProcessor; + @Inject + private PolyProcessor polyProcessor; + + /** + * Temporary handling of destructuring patterns while typing the AST. + */ + void typeDestructuringPattern(RuleEnvironment G, EObject node, ASTMetaInfoCache cache) { + // ArrayLiteral or ObjectLiteral, but plays role of a destructuring pattern + // -> does not really have a type, but use UnknownTypeRef to avoid having + // to deal with this special case whenever asking for type of an expression + cache.storeType((TypableElement) node, undefinedTypeRef(G)); + // for object literals, some additional hacks are required ... + if (node instanceof ObjectLiteral) { + ObjectLiteral olit = (ObjectLiteral) node; + // poly expressions in property name/value pairs expect to be processed as part of the outer poly expression + // -> invoke poly processor for them + // TODO GH-1337 add support for spread operator + for (PropertyAssignment pa : olit.getPropertyAssignments()) { + if (pa instanceof PropertyNameValuePair) { + Expression expr = ((PropertyNameValuePair) pa).getExpression(); + if (expr != null && polyProcessor.isResponsibleFor(expr) && !polyProcessor.isEntryPoint(expr)) { + polyProcessor.inferType(G, expr, cache); + } + } + } + // the defined type of the object literal may still have some DeferredTypeRefs -> remove them + for (DeferredTypeRef dtr : toIterable(filter( + olit.getDefinedType().eAllContents(), DeferredTypeRef.class))) { + + EcoreUtilN4.doWithDeliver(false, () -> EcoreUtil.replace(dtr, undefinedTypeRef(G)), dtr.eContainer()); + } + // add types for property assignments + for (PropertyAssignment pa : olit.getPropertyAssignments()) { + cache.storeType(pa, undefinedTypeRef(G)); + } + } + // here we basically turn off the fail-fast approach within the destructuring pattern + for (EObject elem : toIterable(node.eAllContents())) { + if (elem instanceof ObjectLiteral || elem instanceof PropertyAssignment + || elem instanceof ArrayLiteral || elem instanceof ArrayElement) { + + if (cache.getTypeFailSafe((TypableElement) elem) == null) { + cache.storeType((TypableElement) elem, undefinedTypeRef(G)); + } + } + } + } + + /** + * Temporary handling of forward references within destructuring patterns. + */ + TypeRef handleForwardReferenceWhileTypingDestructuringPattern(RuleEnvironment G, TypableElement node, + ASTMetaInfoCache cache) { + + EObject parent = node.eContainer(); + boolean isCyclicForwardReference = cache.astNodesCurrentlyBeingTyped.contains(node); + if (isCyclicForwardReference) { + if (parent instanceof VariableBinding && ((VariableBinding) parent).getExpression() == node) { + // we get here when typing the second 'b' in 'var [a,b] = [0,b,2];' + return anyTypeRef(G); + } else if (parent instanceof ForStatement && ((ForStatement) parent).getExpression() == node) { + // we get here when typing the second 'a' in 'for(var [a] of [[a]]) {}' + return anyTypeRef(G); + } + } + + log(0, "===START of other identifiable sub-tree"); + RuleEnvironment G_fresh = RuleEnvironmentExtensions.wrap(G); // don't use a new, empty environment here + // (required for recursion guards) + astProcessor.processSubtree(G_fresh, node, cache, 0); // note how we reset the indent level + cache.forwardProcessedSubTrees.add(node); + log(0, "===END of other identifiable sub-tree"); + return cache.getType(G, node); + } + + boolean isForwardReferenceWhileTypingDestructuringPattern(EObject obj) { + if (obj instanceof Expression) { + EObject parent = obj.eContainer(); + if (parent instanceof ForStatement) { + return DestructureUtils.isTopOfDestructuringForStatement(parent); + } + if (parent instanceof AssignmentExpression) { + return DestructureUtils.isTopOfDestructuringAssignment(parent); + } + return parent instanceof VariableBinding + || parent instanceof BindingElement + || (parent instanceof VariableDeclaration && parent.eContainer() instanceof BindingElement); + } + return false; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/DestructureProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/DestructureProcessor.xtend deleted file mode 100644 index cbfde29496..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/DestructureProcessor.xtend +++ /dev/null @@ -1,137 +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.emf.ecore.util.EcoreUtil -import org.eclipse.n4js.n4JS.ArrayElement -import org.eclipse.n4js.n4JS.ArrayLiteral -import org.eclipse.n4js.n4JS.AssignmentExpression -import org.eclipse.n4js.n4JS.BindingElement -import org.eclipse.n4js.n4JS.DestructureUtils -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.ForStatement -import org.eclipse.n4js.n4JS.ObjectLiteral -import org.eclipse.n4js.n4JS.PropertyAssignment -import org.eclipse.n4js.n4JS.PropertyNameValuePair -import org.eclipse.n4js.n4JS.VariableBinding -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.TypableElement -import org.eclipse.n4js.typesystem.utils.RuleEnvironment -import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions -import org.eclipse.n4js.utils.EcoreUtilN4 - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * Deals with destructuring patterns during post processing of an N4JS resource (only the destructuring pattern; - * the value to be destructured is handled normally by the other processors). - *

- * TODO clean up handling of destructuring patterns during AST traversal, IDE-1714 - */ -@Singleton -package class DestructureProcessor extends AbstractProcessor { - - @Inject - private ASTProcessor astProcessor; - @Inject - private PolyProcessor polyProcessor; - - /** - * Temporary handling of destructuring patterns while typing the AST. - */ - def void typeDestructuringPattern(RuleEnvironment G, EObject node, ASTMetaInfoCache cache, int indentLevel) { - // ArrayLiteral or ObjectLiteral, but plays role of a destructuring pattern - // -> does not really have a type, but use UnknownTypeRef to avoid having - // to deal with this special case whenever asking for type of an expression - cache.storeType(node as TypableElement, G.undefinedTypeRef); - // for object literals, some additional hacks are required ... - if (node instanceof ObjectLiteral) { - // poly expressions in property name/value pairs expect to be processed as part of the outer poly expression - // -> invoke poly processor for them - // TODO GH-1337 add support for spread operator - node.propertyAssignments // - .filter(PropertyNameValuePair) // - .map[expression] // - .filterNull // - .filter[polyProcessor.isResponsibleFor(it) && !polyProcessor.isEntryPoint(it)] // - .forEach [ - polyProcessor.inferType(G, it, cache); - ]; - // the defined type of the object literal may still have some DeferredTypeRefs -> remove them - node.definedType.eAllContents.filter(DeferredTypeRef).forEach [ dtr | - EcoreUtilN4.doWithDeliver(false, [ - EcoreUtil.replace(dtr, G.undefinedTypeRef); - ], dtr.eContainer); - ] - // add types for property assignments - node.propertyAssignments.forEach [ - cache.storeType(it, G.undefinedTypeRef); - ] - } - // here we basically turn off the fail-fast approach within the destructuring pattern - node.eAllContents // - .filter[ - it instanceof ObjectLiteral || it instanceof PropertyAssignment - || it instanceof ArrayLiteral || it instanceof ArrayElement - ] // - .filter[cache.getTypeFailSafe(it as TypableElement)===null] // - .forEach[ - cache.storeType(it as TypableElement, G.undefinedTypeRef); - ]; - } - - /** - * Temporary handling of forward references within destructuring patterns. - */ - def TypeRef handleForwardReferenceWhileTypingDestructuringPattern(RuleEnvironment G, TypableElement node, - ASTMetaInfoCache cache) { - - val parent = node.eContainer(); - val isCyclicForwardReference = cache.astNodesCurrentlyBeingTyped.contains(node); - if(isCyclicForwardReference) { - if(parent instanceof VariableBinding && (parent as VariableBinding).expression===node) { - // we get here when typing the second 'b' in 'var [a,b] = [0,b,2];' - return G.anyTypeRef; - } else if(parent instanceof ForStatement && (parent as ForStatement).expression===node) { - // we get here when typing the second 'a' in 'for(var [a] of [[a]]) {}' - return G.anyTypeRef; - } - } - - log(0, "===START of other identifiable sub-tree"); - val G_fresh = RuleEnvironmentExtensions.wrap(G); // don't use a new, empty environment here (required for recursion guards) - astProcessor.processSubtree(G_fresh, node, cache, 0); // note how we reset the indent level - cache.forwardProcessedSubTrees.add(node); - log(0, "===END of other identifiable sub-tree"); - return cache.getType(G, node); - } - - def boolean isForwardReferenceWhileTypingDestructuringPattern(EObject obj) { - if (obj instanceof Expression) { - val parent = obj.eContainer; - if (parent instanceof ForStatement) { - return DestructureUtils.isTopOfDestructuringForStatement(parent); - } - if (parent instanceof AssignmentExpression) { - return DestructureUtils.isTopOfDestructuringAssignment(parent) - } - return parent instanceof VariableBinding - || parent instanceof BindingElement - || (parent instanceof VariableDeclaration && parent.eContainer instanceof BindingElement) - } - return false; - } -} 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: + *

    + *
  • if given expression is non-poly: simply return its type
    + * (note: in this case this method won't process nested expressions in any way). + *
  • if given expression is poly: + *
      + *
    1. introduce a new inference variable to the given inference context for each type to be inferred for the given + * poly expression (usually only 1, but may be several, e.g. for a function expression we introduce an inference + * variable for the return type and each fpar), + *
    2. add appropriate constraints to the given inference context, + *
    3. recursively invoke this method for nested expressions (no matter if poly or non-poly). + *
    4. register to the given inference context an onSolved handler that will - after the inference + * context will have been solved - add all required final types for 'expr' and its non-expression children + * (e.g. fpars) to the typing cache. + *
    5. return temporary type of the given expression expr. + *
    + *
+ * 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: - *

    - *
  • if given expression is non-poly: simply return its type
    - * (note: in this case this method won't process nested expressions in any way). - *
  • if given expression is poly: - *
      - *
    1. introduce a new inference variable to the given inference context for each type to be inferred for the - * given poly expression (usually only 1, but may be several, e.g. for a function expression we introduce an - * inference variable for the return type and each fpar), - *
    2. add appropriate constraints to the given inference context, - *
    3. recursively invoke this method for nested expressions (no matter if poly or non-poly). - *
    4. register to the given inference context an onSolved handler that will - after the inference - * context will have been solved - add all required final types for 'expr' and its non-expression - * children (e.g. fpars) to the typing cache. - *
    5. return temporary type of the given expression expr. - *
    - *
- * 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/postprocessing/PolyProcessor_ArrayLiteral.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_ArrayLiteral.java new file mode 100644 index 0000000000..9462748ec2 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_ArrayLiteral.java @@ -0,0 +1,400 @@ +/** + * 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.anyTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.arrayNType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.arrayNTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.arrayType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.arrayTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.isArrayN; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.isIterableN; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.iterableType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.iterableTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.stringType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.stringTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.undefinedTypeRef; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.eclipse.n4js.n4JS.ArrayElement; +import org.eclipse.n4js.n4JS.ArrayLiteral; +import org.eclipse.n4js.n4JS.ArrayPadding; +import org.eclipse.n4js.n4JS.DestructureUtils; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory; +import org.eclipse.n4js.ts.typeRefs.UnionTypeExpression; +import org.eclipse.n4js.ts.types.InferenceVariable; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TInterface; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.ts.types.TypeVariable; +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.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.xtext.xbase.lib.IterableExtensions; + +import com.google.common.base.Optional; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * {@link PolyProcessor} delegates here for processing array literals. + * + * @see PolyProcessor#inferType(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,ASTMetaInfoCache) + * @see PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache) + */ +@Singleton +class PolyProcessor_ArrayLiteral extends AbstractPolyProcessor { + @Inject + private PolyProcessor polyProcessor; + @Inject + private N4JSTypeSystem ts; + @Inject + private TypeSystemHelper tsh; + + /** + * BEFORE CHANGING THIS METHOD, READ THIS: + * {@link PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache)} + */ + TypeRef processArrayLiteral(RuleEnvironment G, ArrayLiteral arrLit, TypeRef expectedTypeRef, + InferenceContext infCtx, ASTMetaInfoCache cache) { + + // note: we do not have the case !arrLit.isPoly here, as in the other poly processors + // (array literals are always poly, because they cannot be explicitly typed in N4JS) + + int numOfElems = arrLit.getElements().size(); + + // we have to analyze the type expectation: + // 1. we have to know up-front whether we aim for an actual type of Array/Iterable or for ArrayN/IterableN + // 2. we have to know if we have concrete expectations for the element type(s) + // To do so, we prepare a helper variable 'expectedElemTypeRefs' + List expectedElemTypeRefs = getExpectedElemTypeRefs(G, expectedTypeRef); + + // hack: faking an expectation of ArrayN<...> here + // TODO instead we should get such an expectation in these cases from expectedType judgment! + boolean isValueToBeDestructured = DestructureUtils.isArrayOrObjectLiteralBeingDestructured(arrLit); + if (isValueToBeDestructured) { + while (expectedElemTypeRefs.size() < numOfElems) + expectedElemTypeRefs.add(anyTypeRef(G)); + } + + // performance tweak: + boolean haveUsableExpectedType = !expectedElemTypeRefs.isEmpty(); + if (!haveUsableExpectedType && !TypeUtils.isInferenceVariable(expectedTypeRef)) { + // no type expectation or some entirely wrong type expectation (i.e. other than Array, ArrayN) + // -> just derive type from elements (and do not introduce a new inference variable for this ArrayLiteral!) + List elemTypeRefs = new ArrayList<>(); + List nonNullElems = toList( + IterableExtensions.filter(arrLit.getElements(), ae -> ae.getExpression() != null)); + for (ArrayElement arrElem : nonNullElems) { + var arrElemTypeRef = polyProcessor.processExpr(G, arrElem.getExpression(), null, infCtx, cache); + arrElemTypeRef = ts.upperBoundWithReopen(G, arrElemTypeRef); + if (arrElem.isSpread()) { + // more than one in case of ArrayN; none in case of invalid value after spread operator + elemTypeRefs.addAll(extractSpreadTypeRefs(G, arrElemTypeRef)); + } else { + elemTypeRefs.add(arrElemTypeRef); + } + } + + infCtx.onSolved(solution -> handleOnSolvedPerformanceTweak(G, cache, arrLit, expectedElemTypeRefs)); + + TypeRef unionOfElemTypes = (!elemTypeRefs.isEmpty()) + ? tsh.createUnionType(G, elemTypeRefs.toArray(new TypeRef[0])) + : anyTypeRef(G); + return arrayTypeRef(G, unionOfElemTypes); + } + + int resultLen = getResultLength(arrLit, expectedElemTypeRefs); + TypeVariable[] resultInfVars = infCtx.newInferenceVariables(resultLen); + + processElements(G, cache, infCtx, arrLit, resultLen, resultInfVars); + + TypeRef resultTypeRef = getResultTypeRef(G, resultLen, resultInfVars); + + // register onSolved handlers to add final types to cache (i.e. may not contain inference variables) + infCtx.onSolved(solution -> handleOnSolved(G, cache, arrLit, expectedElemTypeRefs, resultTypeRef, solution)); + + // return temporary type of arrLit (i.e. may contain inference variables) + return resultTypeRef; + } + + /** + * The return value is as follows: + *
    + *
  • #[ T ] for an expectedTypeRef of the form Array<T> or Iterable<T>,
  • + *
  • #[ T1, T2, ..., TN ] for an expectedTypeRef of the form ArrayN<T1,T2,...,TN>,
  • + *
  • #[] for any other kind of expectedTypeRef
  • + *
+ */ + private List getExpectedElemTypeRefs(RuleEnvironment G, TypeRef expectedTypeRef) { + if (expectedTypeRef != null) { + List candidateTypeRefs = (expectedTypeRef instanceof UnionTypeExpression) + ? ((UnionTypeExpression) expectedTypeRef).getTypeRefs() + : List.of(expectedTypeRef); + TInterface iterableType = iterableType(G); + TClass arrayType = arrayType(G); + for (TypeRef candidateTypeRef : candidateTypeRefs) { + Type declType = candidateTypeRef.getDeclaredType(); + if (declType == iterableType + || declType == arrayType + || isIterableN(G, declType) + || isArrayN(G, declType)) { + List extractedTypeRefs = tsh.extractIterableElementTypes(G, candidateTypeRef); + if (extractedTypeRefs.size() > 0) { + return extractedTypeRefs; // will have len>1 iff expectation is IterableN + } + } + } + } + return new ArrayList<>(); // no or invalid type expectation + } + + /** + * Makes a best effort for building a type in case something went awry. It's only non-trivial in case we have an + * expectation of IterableN. + */ + private TypeRef buildFallbackTypeForArrayLiteral(boolean isArrayN, int resultLen, + List elemTypeRefsWithLiteralTypes, List expectedElemTypeRefs, RuleEnvironment G) { + + List elemTypeRefs = toList(map( + elemTypeRefsWithLiteralTypes, elem -> N4JSLanguageUtils.getLiteralTypeBase(G, elem))); + + if (isArrayN) { + TypeRef[] typeArgs = new TypeRef[resultLen]; + for (var i = 0; i < resultLen; i++) { + boolean isLastElem = i == (resultLen - 1); + TypeRef typeRef = null; + if (isLastElem && elemTypeRefs.size() > resultLen) { + // special case: + // we are at the last element AND we actually have more elements than we expect elements + // -> have to check all remaining elements against the last expectation! + List allRemainingElementTypeRefs = new ArrayList<>(); + TypeRef currExpectedElemTypeRef = expectedElemTypeRefs.get(i); + + // if all remaining elements are a subtype of the last expectation, then use expectation, otherwise + // form union + boolean allMatch = true; + for (var j = i; j < elemTypeRefs.size(); j++) { + + TypeRef currElementTypeRef = elemTypeRefs.get(j); + allRemainingElementTypeRefs.add(currElementTypeRef); + + if (allMatch) { // don't try further subtype checks if already failed + boolean actualIsSubtypeOfExpected = ts.subtypeSucceeded(G, currElementTypeRef, + currExpectedElemTypeRef); + if (!actualIsSubtypeOfExpected) { + allMatch = false; + } + } + } + if (allMatch) { + // use expected type + typeRef = currExpectedElemTypeRef; + } else { + // use actual types (will lead to follow-up errors caught by validations) + typeRef = tsh.createUnionType(G, allRemainingElementTypeRefs.toArray(new TypeRef[0])); + } + } else { + TypeRef currElemTypeRef = elemTypeRefs.get(i); + TypeRef currExpectedElemTypeRef = expectedElemTypeRefs.get(i); + boolean actualIsSubtypeOfExpected = ts.subtypeSucceeded(G, currElemTypeRef, + currExpectedElemTypeRef); + if (actualIsSubtypeOfExpected) { + // use expected type + typeRef = currExpectedElemTypeRef; + } else { + // use actual type (will lead to follow-up errors caught by validations) + typeRef = currElemTypeRef; + } + } + typeArgs[i] = typeRef; + } + + if (elemTypeRefs.size() > resultLen) { + // replace last entry in 'typeArgs' with union of all remaining in elemTypeRefs + TypeRef[] remaining = Arrays.copyOfRange(elemTypeRefs.toArray(new TypeRef[0]), resultLen - 1, + elemTypeRefs.size()); + + typeArgs[resultLen - 1] = tsh.createUnionType(G, remaining); + } + + return arrayNTypeRef(G, resultLen, typeArgs); + } else { + TypeRef unionOfElemTypes = (!elemTypeRefs.isEmpty()) + ? tsh.createUnionType(G, elemTypeRefs.toArray(new TypeRef[0])) + : anyTypeRef(G); + return arrayTypeRef(G, unionOfElemTypes); + } + } + + /** + * choose correct number of type arguments in our to-be-created resultTypeRef (always 1 for Array or Iterable + * but N for ArrayN<..>, e.g. 3 for Array3) + */ + private int getResultLength(ArrayLiteral arrLit, List expectedElemTypeRefs) { + int numOfElems = arrLit.getElements().size(); + int lenA = Math.min( + expectedElemTypeRefs.size(), // use number of type arguments provided by type expectation as a basis + numOfElems // ... but never more than we have elements in the array literal + ); + + int lenB = Math.min( + lenA, + BuiltInTypeScope.ITERABLE_N__MAX_LEN // ... and never more than the max. allowed number of type + // arguments for ArrayN + ); + + int resultLen = Math.max( + lenB, + 1 // ... but at least 1 (even if numOfElems is 0, for example) + ); + return resultLen; + } + + /** + * Creates temporary type (i.e. may contain inference variables): + *
    + *
  • Array (where T is a new inference variable) or
  • + *
  • ArrayN (where T1,...TN are new inference variables, N>=2)
  • + *
+ */ + private TypeRef getResultTypeRef(RuleEnvironment G, int resultLen, TypeVariable[] resultInfVars) { + boolean isArrayN = resultLen >= 2; + TClass declaredType = (isArrayN) ? arrayNType(G, resultLen) : arrayType(G); + List typeArgs = toList( + map(Arrays.asList(resultInfVars), v -> TypeUtils.createTypeRef(v))); + TypeRef resultTypeRef = TypeUtils.createTypeRef(declaredType, typeArgs.toArray(new ParameterizedTypeRef[0])); + return resultTypeRef; + } + + /** + * for each array element, add a constraint to ensure that its corresponding infVar in result type will be a super + * type of the array element's expression + */ + private void processElements(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, + ArrayLiteral arrLit, + int resultLen, TypeVariable[] resultInfVars) { + int numOfElems = arrLit.getElements().size(); + for (var idxElem = 0; idxElem < numOfElems; idxElem++) { + ArrayElement currElem = arrLit.getElements().get(idxElem); + if (currElem == null || currElem.getExpression() == null) { + // currElem is null, or has no expression (broken AST), or is an ArrayPadding element + // -> ignore (no constraint to add) + } else { + // currElem is a valid ArrayElement with an expression + // -> add constraint currElemTypeRef <: Ti (Ti being the corresponding inf. variable in resultTypeRef) + int idxResult = Math.min(idxElem, resultLen - 1); + TypeVariable currResultInfVar = resultInfVars[idxResult]; + TypeRef currResultInfVarTypeRef = TypeUtils.createTypeRef(currResultInfVar); + TypeRef currExpectedTypeRef = (currElem.isSpread()) + ? iterableTypeRef(G, TypeUtils.createWildcardExtends(currResultInfVarTypeRef)) + : currResultInfVarTypeRef; + TypeRef currElemTypeRef = polyProcessor.processExpr(G, currElem.getExpression(), currExpectedTypeRef, + infCtx, cache); + infCtx.addConstraint(currElemTypeRef, currExpectedTypeRef, Variance.CO); + } + } + } + + /** + * Writes final types to cache. + */ + private void handleOnSolvedPerformanceTweak(RuleEnvironment G, ASTMetaInfoCache cache, ArrayLiteral arrLit, + List expectedElemTypeRefs) { + List betterElemTypeRefs = storeTypesOfArrayElements(G, cache, arrLit); + TypeRef fallbackTypeRef = buildFallbackTypeForArrayLiteral(false, 1, betterElemTypeRefs, expectedElemTypeRefs, + G); + cache.storeType(arrLit, fallbackTypeRef); + } + + /** + * Writes final types to cache. + */ + private void handleOnSolved(RuleEnvironment G, ASTMetaInfoCache cache, ArrayLiteral arrLit, + List expectedElemTypeRefs, TypeRef resultTypeRef, + Optional> solution) { + int resultLen = getResultLength(arrLit, expectedElemTypeRefs); + boolean isArrayN = resultLen >= 2; + if (solution.isPresent()) { + // success case + TypeRef typeRef = applySolution(resultTypeRef, G, solution.get()); + cache.storeType(arrLit, typeRef); + } else { + // failure case (unsolvable constraint system) + List betterElemTypeRefs = toList(map( + arrLit.getElements(), ae -> getFinalResultTypeOfArrayElement(G, ae, Optional.absent()))); + TypeRef typeRef = buildFallbackTypeForArrayLiteral(isArrayN, resultLen, betterElemTypeRefs, + expectedElemTypeRefs, G); + cache.storeType(arrLit, typeRef); + } + storeTypesOfArrayElements(G, cache, arrLit); + } + + // PolyProcessor#isResponsibleFor(TypableElement) claims responsibility of AST nodes of type 'ArrayElement' + // contained in an ArrayLiteral which is poly, so we are responsible for storing the types of those + // 'ArrayElement' nodes in cache + // (note: compare this with similar handling of 'Argument' nodes in PolyProcessor_CallExpression) + private List storeTypesOfArrayElements(RuleEnvironment G, ASTMetaInfoCache cache, ArrayLiteral arrLit) { + List storedElemTypeRefs = new ArrayList<>(); + for (ArrayElement currElem : arrLit.getElements()) { + if (currElem instanceof ArrayPadding) { + cache.storeType(currElem, undefinedTypeRef(G)); + } else { + TypeRef currElemTypeRef = getFinalResultTypeOfArrayElement(G, currElem, + Optional.of(storedElemTypeRefs)); + cache.storeType(currElem, currElemTypeRef); + } + } + return storedElemTypeRefs; + } + + private TypeRef getFinalResultTypeOfArrayElement(RuleEnvironment G, ArrayElement currElem, + Optional> addTypeRefsHere) { + Expression currExpr = currElem == null ? null : currElem.getExpression(); + TypeRef currElemTypeRef = (currExpr != null) ? getFinalResultTypeOfNestedPolyExpression(currExpr) : null; + if (currElemTypeRef != null && currElem != null) { + currElemTypeRef = ts.upperBoundWithReopen(G, currElemTypeRef); + List currElemTypeRefs = (currElem.isSpread()) ? extractSpreadTypeRefs(G, currElemTypeRef) + : List.of(currElemTypeRef); + if (addTypeRefsHere.isPresent()) { + addTypeRefsHere.get().addAll(currElemTypeRefs); + } + return tsh.createUnionType(G, currElemTypeRefs.toArray(new TypeRef[0])); + } + return TypeRefsFactory.eINSTANCE.createUnknownTypeRef(); + } + + private List extractSpreadTypeRefs(RuleEnvironment G, TypeRef typeRef) { + // case 1: built-in type string + if (typeRef instanceof ParameterizedTypeRef) { + if (typeRef.getDeclaredType() == stringType(G)) { + return List.of(stringTypeRef(G)); // spreading a string yields zero or more strings + } + } + // case 2: Iterable or ArrayN + return tsh.extractIterableElementTypes(G, typeRef); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_ArrayLiteral.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_ArrayLiteral.xtend deleted file mode 100644 index 7111a4aedd..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_ArrayLiteral.xtend +++ /dev/null @@ -1,361 +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.common.base.Optional -import com.google.inject.Inject -import com.google.inject.Singleton -import java.util.Arrays -import java.util.Collection -import java.util.List -import java.util.Map -import org.eclipse.n4js.n4JS.ArrayElement -import org.eclipse.n4js.n4JS.ArrayLiteral -import org.eclipse.n4js.n4JS.ArrayPadding -import org.eclipse.n4js.n4JS.DestructureUtils -import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory -import org.eclipse.n4js.ts.typeRefs.UnionTypeExpression -import org.eclipse.n4js.ts.types.InferenceVariable -import org.eclipse.n4js.ts.types.TypeVariable -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.utils.RuleEnvironment -import org.eclipse.n4js.typesystem.utils.TypeSystemHelper -import org.eclipse.n4js.utils.N4JSLanguageUtils - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * {@link PolyProcessor} delegates here for processing array literals. - * - * @see PolyProcessor#inferType(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,ASTMetaInfoCache) - * @see PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache) - */ -@Singleton -package class PolyProcessor_ArrayLiteral extends AbstractPolyProcessor { - @Inject - private PolyProcessor polyProcessor; - @Inject - private N4JSTypeSystem ts; - @Inject - private TypeSystemHelper tsh; - - /** - * BEFORE CHANGING THIS METHOD, READ THIS: - * {@link PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache)} - */ - def package TypeRef processArrayLiteral(RuleEnvironment G, ArrayLiteral arrLit, TypeRef expectedTypeRef, - InferenceContext infCtx, ASTMetaInfoCache cache) { - - // note: we do not have the case !arrLit.isPoly here, as in the other poly processors - // (array literals are always poly, because they cannot be explicitly typed in N4JS) - - val numOfElems = arrLit.elements.size; - - // we have to analyze the type expectation: - // 1. we have to know up-front whether we aim for an actual type of Array/Iterable or for ArrayN/IterableN - // 2. we have to know if we have concrete expectations for the element type(s) - // To do so, we prepare a helper variable 'expectedElemTypeRefs' - val expectedElemTypeRefs = getExpectedElemTypeRefs(G, expectedTypeRef); - - -// hack: faking an expectation of ArrayN<...> here -// TODO instead we should get such an expectation in these cases from expectedType judgment! -val isValueToBeDestructured = DestructureUtils.isArrayOrObjectLiteralBeingDestructured(arrLit); -if(isValueToBeDestructured) { - while(expectedElemTypeRefs.size < numOfElems) - expectedElemTypeRefs.add(G.anyTypeRef); -} - - // performance tweak: - val haveUsableExpectedType = !expectedElemTypeRefs.empty; - if (!haveUsableExpectedType && !TypeUtils.isInferenceVariable(expectedTypeRef)) { - // no type expectation or some entirely wrong type expectation (i.e. other than Array, ArrayN) - // -> just derive type from elements (and do not introduce a new inference variable for this ArrayLiteral!) - val elemTypeRefs = newArrayList; - val nonNullElems = arrLit.elements.filter[expression !== null]; - for (arrElem : nonNullElems) { - var arrElemTypeRef = polyProcessor.processExpr(G, arrElem.expression, null, infCtx, cache); - arrElemTypeRef = ts.upperBoundWithReopen(G, arrElemTypeRef); - if (arrElem.spread) { - elemTypeRefs += extractSpreadTypeRefs(G, arrElemTypeRef); // more than one in case of ArrayN; none in case of invalid value after spread operator - } else { - elemTypeRefs += arrElemTypeRef; - } - } - - infCtx.onSolved [ solution | handleOnSolvedPerformanceTweak(G, cache, arrLit, expectedElemTypeRefs) ]; - - val unionOfElemTypes = if (!elemTypeRefs.empty) tsh.createUnionType(G, elemTypeRefs) else G.anyTypeRef; - return G.arrayTypeRef(unionOfElemTypes); - } - - val resultLen = getResultLength(arrLit, expectedElemTypeRefs); - val TypeVariable[] resultInfVars = infCtx.newInferenceVariables(resultLen); - - processElements(G, cache, infCtx, arrLit, resultLen, resultInfVars); - - val TypeRef resultTypeRef = getResultTypeRef(G, resultLen, resultInfVars); - - // register onSolved handlers to add final types to cache (i.e. may not contain inference variables) - infCtx.onSolved [ solution | handleOnSolved(G, cache, arrLit, expectedElemTypeRefs, resultTypeRef, solution) ]; - - // return temporary type of arrLit (i.e. may contain inference variables) - return resultTypeRef; - } - - /** - * The return value is as follows: - *
    - *
  • #[ T ] for an expectedTypeRef of the form Array<T> or Iterable<T>,
  • - *
  • #[ T1, T2, ..., TN ] for an expectedTypeRef of the form ArrayN<T1,T2,...,TN>,
  • - *
  • #[] for any other kind of expectedTypeRef
  • - *
- */ - private def List getExpectedElemTypeRefs(RuleEnvironment G, TypeRef expectedTypeRef) { - if (expectedTypeRef !== null) { - val candidateTypeRefs = if (expectedTypeRef instanceof UnionTypeExpression) { - expectedTypeRef.typeRefs - } else { - #[ expectedTypeRef ] - }; - val iterableType = G.iterableType; - val arrayType = G.arrayType; - for (candidateTypeRef : candidateTypeRefs) { - val declType = candidateTypeRef.declaredType; - if (declType === iterableType - || declType === arrayType - || G.isIterableN(declType) - || G.isArrayN(declType)) { - val extractedTypeRefs = tsh.extractIterableElementTypes(G, candidateTypeRef); - if (extractedTypeRefs.size > 0) { - return extractedTypeRefs; // will have len>1 iff expectation is IterableN - } - } - } - } - return newArrayList // no or invalid type expectation - } - - /** - * Makes a best effort for building a type in case something went awry. It's only non-trivial in case we have an - * expectation of IterableN. - */ - private def TypeRef buildFallbackTypeForArrayLiteral(boolean isArrayN, int resultLen, - List elemTypeRefsWithLiteralTypes, List expectedElemTypeRefs, RuleEnvironment G) { - - val elemTypeRefs = elemTypeRefsWithLiteralTypes.map[N4JSLanguageUtils.getLiteralTypeBase(G, it)].toList; - - if (isArrayN) { - val typeArgs = newArrayOfSize(resultLen); - for (var i = 0; i < resultLen; i++) { - val boolean isLastElem = i === (resultLen - 1); - var TypeRef typeRef = null; - if (isLastElem && elemTypeRefs.size > resultLen) { - // special case: - // we are at the last element AND we actually have more elements than we expect elements - // -> have to check all remaining elements against the last expectation! - val allRemainingElementTypeRefs = newArrayList; - val currExpectedElemTypeRef = expectedElemTypeRefs.get(i); - - // if all remaining elements are a subtype of the last expectation, then use expectation, otherwise form union - var boolean allMatch = true; - for (var j = i; j < elemTypeRefs.size; j++) { - - val currElementTypeRef = elemTypeRefs.get(j); - allRemainingElementTypeRefs.add(currElementTypeRef); - - if (allMatch) { // don't try further subtype checks if already failed - val actualIsSubtypeOfExpected = ts.subtypeSucceeded(G, currElementTypeRef, - currExpectedElemTypeRef); - if (!actualIsSubtypeOfExpected) { - allMatch = false; - } - } - } - if (allMatch) { - // use expected type - typeRef = currExpectedElemTypeRef; - } else { - // use actual types (will lead to follow-up errors caught by validations) - typeRef = tsh.createUnionType(G, allRemainingElementTypeRefs); - } - } else { - val currElemTypeRef = elemTypeRefs.get(i); - val currExpectedElemTypeRef = expectedElemTypeRefs.get(i); - val actualIsSubtypeOfExpected = ts.subtypeSucceeded(G, currElemTypeRef, currExpectedElemTypeRef); - if (actualIsSubtypeOfExpected) { - // use expected type - typeRef = currExpectedElemTypeRef; - } else { - // use actual type (will lead to follow-up errors caught by validations) - typeRef = currElemTypeRef; - } - } - typeArgs.set(i, typeRef); - } - - if (elemTypeRefs.size > resultLen) { - // replace last entry in 'typeArgs' with union of all remaining in elemTypeRefs - val remaining = Arrays.copyOfRange(elemTypeRefs, resultLen - 1, elemTypeRefs.size); - typeArgs.set(resultLen - 1, tsh.createUnionType(G, remaining)); - } - - return G.arrayNTypeRef(resultLen, typeArgs); - } else { - val unionOfElemTypes = if (!elemTypeRefs.empty) tsh.createUnionType(G, elemTypeRefs) else G.anyTypeRef; - return G.arrayTypeRef(unionOfElemTypes); - } - } - - /** - * choose correct number of type arguments in our to-be-created resultTypeRef - * (always 1 for Array or Iterable but N for ArrayN<..>, e.g. 3 for Array3) - */ - private def int getResultLength(ArrayLiteral arrLit, List expectedElemTypeRefs) { - val numOfElems = arrLit.elements.size; - val lenA = Math.min( - expectedElemTypeRefs.size, // use number of type arguments provided by type expectation as a basis - numOfElems // ... but never more than we have elements in the array literal - ); - - val lenB = Math.min( - lenA, - BuiltInTypeScope.ITERABLE_N__MAX_LEN // ... and never more than the max. allowed number of type arguments for ArrayN - ); - - val resultLen = Math.max( - lenB, - 1 // ... but at least 1 (even if numOfElems is 0, for example) - ); - return resultLen; - } - - /** - * Creates temporary type (i.e. may contain inference variables): - *
    - *
  • Array (where T is a new inference variable) or
  • - *
  • ArrayN (where T1,...TN are new inference variables, N>=2)
  • - *
- */ - private def TypeRef getResultTypeRef(RuleEnvironment G, int resultLen, TypeVariable[] resultInfVars) { - val isArrayN = resultLen >= 2; - val declaredType = if (isArrayN) G.arrayNType(resultLen) else G.arrayType; - val typeArgs = resultInfVars.map[TypeUtils.createTypeRef(it)]; - val TypeRef resultTypeRef = TypeUtils.createTypeRef(declaredType, typeArgs); - return resultTypeRef; - } - - /** - * for each array element, add a constraint to ensure that its corresponding infVar in result type will be - * a super type of the array element's expression - */ - private def void processElements(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, ArrayLiteral arrLit, - int resultLen, TypeVariable[] resultInfVars - ) { - val numOfElems = arrLit.elements.size; - for (var idxElem = 0; idxElem < numOfElems; idxElem++) { - val currElem = arrLit.elements.get(idxElem); - if (currElem?.expression === null) { - // currElem is null, or has no expression (broken AST), or is an ArrayPadding element - // -> ignore (no constraint to add) - } else { - // currElem is a valid ArrayElement with an expression - // -> add constraint currElemTypeRef <: Ti (Ti being the corresponding inf. variable in resultTypeRef) - val idxResult = Math.min(idxElem, resultLen - 1); - val currResultInfVar = resultInfVars.get(idxResult); - val currResultInfVarTypeRef = TypeUtils.createTypeRef(currResultInfVar); - val currExpectedTypeRef = if (currElem.spread) G.iterableTypeRef(TypeUtils.createWildcardExtends(currResultInfVarTypeRef)) else currResultInfVarTypeRef; - val currElemTypeRef = polyProcessor.processExpr(G, currElem.expression, currExpectedTypeRef, infCtx, cache); - infCtx.addConstraint(currElemTypeRef, currExpectedTypeRef, Variance.CO); - } - } - } - - /** - * Writes final types to cache. - */ - private def void handleOnSolvedPerformanceTweak(RuleEnvironment G, ASTMetaInfoCache cache, ArrayLiteral arrLit, - List expectedElemTypeRefs - ) { - val betterElemTypeRefs = storeTypesOfArrayElements(G, cache, arrLit); - val fallbackTypeRef = buildFallbackTypeForArrayLiteral(false, 1, betterElemTypeRefs, expectedElemTypeRefs, G); - cache.storeType(arrLit, fallbackTypeRef); - } - - /** - * Writes final types to cache. - */ - private def void handleOnSolved(RuleEnvironment G, ASTMetaInfoCache cache, ArrayLiteral arrLit, - List expectedElemTypeRefs, TypeRef resultTypeRef, Optional> solution - ) { - val resultLen = getResultLength(arrLit, expectedElemTypeRefs); - val isArrayN = resultLen >= 2; - if (solution.present) { - // success case - val typeRef = resultTypeRef.applySolution(G, solution.get); - cache.storeType(arrLit, typeRef); - } else { - // failure case (unsolvable constraint system) - val betterElemTypeRefs = arrLit.elements.map[getFinalResultTypeOfArrayElement(G, it, Optional.absent)]; - val typeRef = buildFallbackTypeForArrayLiteral(isArrayN, resultLen, betterElemTypeRefs, expectedElemTypeRefs, G); - cache.storeType(arrLit, typeRef); - } - storeTypesOfArrayElements(G, cache, arrLit); - } - - // PolyProcessor#isResponsibleFor(TypableElement) claims responsibility of AST nodes of type 'ArrayElement' - // contained in an ArrayLiteral which is poly, so we are responsible for storing the types of those - // 'ArrayElement' nodes in cache - // (note: compare this with similar handling of 'Argument' nodes in PolyProcessor_CallExpression) - private def List storeTypesOfArrayElements(RuleEnvironment G, ASTMetaInfoCache cache, ArrayLiteral arrLit) { - val List storedElemTypeRefs = newArrayList; - for (currElem : arrLit.elements) { - if (currElem instanceof ArrayPadding) { - cache.storeType(currElem, G.undefinedTypeRef); - } else { - val currElemTypeRef = getFinalResultTypeOfArrayElement(G, currElem, Optional.of(storedElemTypeRefs)); - cache.storeType(currElem, currElemTypeRef); - } - } - return storedElemTypeRefs; - } - - private def TypeRef getFinalResultTypeOfArrayElement(RuleEnvironment G, ArrayElement currElem, Optional> addTypeRefsHere) { - val currExpr = currElem?.expression; - var currElemTypeRef = if (currExpr!==null) getFinalResultTypeOfNestedPolyExpression(currExpr); - if (currElemTypeRef !== null) { - currElemTypeRef = ts.upperBoundWithReopen(G, currElemTypeRef); - val currElemTypeRefs = if (currElem.spread) extractSpreadTypeRefs(G, currElemTypeRef) else #[ currElemTypeRef ]; - if (addTypeRefsHere.present) { - addTypeRefsHere.get += currElemTypeRefs; - } - return tsh.createUnionType(G, currElemTypeRefs); - } - return TypeRefsFactory.eINSTANCE.createUnknownTypeRef; - } - - private def List extractSpreadTypeRefs(RuleEnvironment G, TypeRef typeRef) { - // case 1: built-in type string - if (typeRef instanceof ParameterizedTypeRef) { - if (typeRef.declaredType === G.stringType) { - return #[ G.stringTypeRef ]; // spreading a string yields zero or more strings - } - } - // case 2: Iterable or ArrayN - return tsh.extractIterableElementTypes(G, typeRef) - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_CallExpression.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_CallExpression.java new file mode 100644 index 0000000000..3433362e83 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_CallExpression.java @@ -0,0 +1,198 @@ +/** + * 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.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.n4js.n4JS.Argument; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.ParameterizedCallExpression; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExprOrRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory; +import org.eclipse.n4js.ts.types.InferenceVariable; +import org.eclipse.n4js.ts.types.TFormalParameter; +import org.eclipse.n4js.ts.types.TypeVariable; +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.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper.Callable; +import org.eclipse.n4js.utils.N4JSLanguageUtils; + +import com.google.common.base.Optional; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * {@link PolyProcessor} delegates here for processing array literals. + * + * @see PolyProcessor#inferType(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,ASTMetaInfoCache) + * @see PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache) + */ +@Singleton +class PolyProcessor_CallExpression extends AbstractPolyProcessor { + + @Inject + private PolyProcessor polyProcessor; + + @Inject + private N4JSTypeSystem ts; + @Inject + private TypeSystemHelper tsh; + + /** + * BEFORE CHANGING THIS METHOD, READ THIS: + * {@link PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache)} + */ + @SuppressWarnings("unused") + TypeRef processCallExpression(RuleEnvironment G, ParameterizedCallExpression callExpr, + TypeRef expectedTypeRef, InferenceContext infCtx, ASTMetaInfoCache cache) { + + Expression target = callExpr.getTarget(); + // IMPORTANT: do not use #processExpr() here (if target is a PolyExpression, it has been processed in a + // separate, independent inference!) + TypeRef targetTypeRef = ts.type(G, target); + Callable callable = tsh.getCallableTypeRef(G, targetTypeRef); + if (callable == null || !callable.getSignatureTypeRef().isPresent()) { + return TypeRefsFactory.eINSTANCE.createUnknownTypeRef(); + } + FunctionTypeExprOrRef sigTypeRef = callable.getSignatureTypeRef().get(); + + if (!N4JSLanguageUtils.isPoly(sigTypeRef, callExpr)) { + TypeRef result = ts.type(G, callExpr); + // do not store in cache (TypeProcessor responsible for storing types of non-poly expressions in cache) + return result; + } + + // create an inference variable for each type parameter of fteor + Map typeParam2infVar = new LinkedHashMap<>(); // type parameter of fteor -> + // inference variable + for (TypeVariable typeParam : sigTypeRef.getTypeVars()) { + typeParam2infVar.put(typeParam, infCtx.newInferenceVariable()); + } + + processParameters(G, cache, infCtx, callExpr, sigTypeRef, typeParam2infVar); + + // create temporary type (i.e. may contain inference variables) + TypeRef resultTypeRefRaw = sigTypeRef.getReturnTypeRef(); + TypeRef resultTypeRef = subst(resultTypeRefRaw, G, typeParam2infVar); + + // register onSolved handlers to add final types to cache (i.e. may not contain inference variables) + infCtx.onSolved(solution -> handleOnSolved(G, cache, callExpr, resultTypeRef, typeParam2infVar, solution)); + + // return temporary type of callExpr (i.e. may contain inference variables) + return resultTypeRef; + } + + /** + * Processes all parameters and derives constraints from their bounds and matching types. + */ + private void processParameters(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, + ParameterizedCallExpression callExpr, FunctionTypeExprOrRef fteor, + Map typeParam2infVar) { + // + // (1) derive constraints from the bounds of the type parameters + // + EList funcTypeVars = fteor.getTypeVars(); + for (TypeVariable currTypeVar : funcTypeVars) { + // don't use currTypeVar.getDeclaredUpperBound() in next line! + TypeRef currUB = fteor.getTypeVarUpperBound(currTypeVar); + if (currUB == null) { + currUB = N4JSLanguageUtils.getTypeVariableImplicitUpperBound(G); + } + // constraint: currTypeVar <: current upper bound + TypeRef leftTypeRef = TypeUtils.createTypeRef(currTypeVar); + TypeRef leftTypeRefSubst = subst(leftTypeRef, G, typeParam2infVar); + TypeRef rightTypeRef = currUB; + TypeRef rightTypeRefSubst = subst(rightTypeRef, G, typeParam2infVar); + infCtx.addConstraint(leftTypeRefSubst, rightTypeRefSubst, Variance.CO); + } + + // + // (2) derive constraints from matching type of each provided argument to type of corresponding fpar + // + int argsSize = callExpr.getArguments().size(); + for (var i = 0; i < argsSize; i++) { + Expression arg = callExpr.getArguments().get(i) == null ? null + : callExpr.getArguments().get(i).getExpression(); + TFormalParameter curr_fpar = fteor.getFparForArgIdx(i); + if (arg != null && curr_fpar != null) { + TypeRef fparTypeRef = curr_fpar.getTypeRef(); + TypeRef fparTypeRefSubst = subst(fparTypeRef, G, typeParam2infVar); + TypeRef argType = polyProcessor.processExpr(G, arg, fparTypeRefSubst, infCtx, cache); + if (argType != null) { + // constraint: argType <: fpar.type + infCtx.addConstraint(fparTypeRefSubst, argType, Variance.CONTRA); + // (note: no substitution in argType required, because it cannot contain any of the new inference + // variables introduced above) + } + } else if (arg != null) { + // more arguments provided than fpars available + // -> this is an error case, but make sure to process the surplus arguments to avoid + // inconsistencies later on (cache misses etc.) + polyProcessor.processExpr(G, arg, null, infCtx, cache); + } + } + + // + // (3) derive constraints from matching expected return type to return type of function + // + // --> not required here (will be done by caller) + } + + /** + * Writes final types to cache. + */ + private void handleOnSolved(RuleEnvironment G, ASTMetaInfoCache cache, ParameterizedCallExpression callExpr, + TypeRef resultTypeRef, Map typeParam2infVar, + Optional> solution) { + if (solution.isPresent()) { + // success case: + cache.storeType(callExpr, applySolution(resultTypeRef, G, solution.get())); + List inferredTypeArgs = toList(map(typeParam2infVar.values(), iv -> solution.get().get(iv))); + cache.storeInferredTypeArgs(callExpr, inferredTypeArgs); + } else { + // failure case (unsolvable constraint system) + // to avoid leaking inference variables, replace them by their original type parameter + Map fakeSolution = new HashMap<>(); + for (Entry e : typeParam2infVar.entrySet()) { + fakeSolution.put(e.getValue(), TypeUtils.createTypeRef(e.getKey())); + } + cache.storeType(callExpr, applySolution(resultTypeRef, G, fakeSolution)); + cache.storeInferredTypeArgs(callExpr, Collections.emptyList()); + } + // PolyProcessor#isResponsibleFor(TypableElement) claims responsibility of AST nodes of type 'Argument' + // contained in a ParameterizedCallExpression which is poly, so we are responsible for storing the types of + // those 'Argument' nodes in cache + // (note: compare this with similar handling of 'ArrayElement' nodes in PolyProcessor_ArrayLiteral) + for (Argument arg : callExpr.getArguments()) { + Expression expr = arg == null ? null : arg.getExpression(); + TypeRef exprType = (expr == null) ? null : getFinalResultTypeOfNestedPolyExpression(expr); + if (exprType != null) { + cache.storeType(arg, exprType); + } else { + cache.storeType(arg, TypeRefsFactory.eINSTANCE.createUnknownTypeRef()); + } + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_CallExpression.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_CallExpression.xtend deleted file mode 100644 index 42d0d6f62b..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_CallExpression.xtend +++ /dev/null @@ -1,177 +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.common.base.Optional -import com.google.inject.Inject -import com.google.inject.Singleton -import java.util.Map -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.ParameterizedCallExpression -import org.eclipse.n4js.ts.typeRefs.FunctionTypeExprOrRef -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory -import org.eclipse.n4js.ts.types.InferenceVariable -import org.eclipse.n4js.ts.types.TFormalParameter -import org.eclipse.n4js.ts.types.TypeVariable -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.utils.RuleEnvironment -import org.eclipse.n4js.typesystem.utils.TypeSystemHelper -import org.eclipse.n4js.utils.N4JSLanguageUtils - -/** - * {@link PolyProcessor} delegates here for processing array literals. - * - * @see PolyProcessor#inferType(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,ASTMetaInfoCache) - * @see PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache) - */ -@Singleton -package class PolyProcessor_CallExpression extends AbstractPolyProcessor { - - @Inject - private PolyProcessor polyProcessor; - - @Inject - private N4JSTypeSystem ts; - @Inject - private TypeSystemHelper tsh; - - /** - * BEFORE CHANGING THIS METHOD, READ THIS: - * {@link PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache)} - */ - def package TypeRef processCallExpression(RuleEnvironment G, ParameterizedCallExpression callExpr, - TypeRef expectedTypeRef, InferenceContext infCtx, ASTMetaInfoCache cache) { - - val target = callExpr.target; - // IMPORTANT: do not use #processExpr() here (if target is a PolyExpression, it has been processed in a separate, independent inference!) - val targetTypeRef = ts.type(G, target); - val callable = tsh.getCallableTypeRef(G, targetTypeRef); - if (callable === null || !callable.signatureTypeRef.present) - return TypeRefsFactory.eINSTANCE.createUnknownTypeRef; - val sigTypeRef = callable.signatureTypeRef.get(); - - if (!N4JSLanguageUtils.isPoly(sigTypeRef, callExpr)) { - val result = ts.type(G, callExpr); - // do not store in cache (TypeProcessor responsible for storing types of non-poly expressions in cache) - return result; - } - - // create an inference variable for each type parameter of fteor - val Map typeParam2infVar = newLinkedHashMap // type parameter of fteor -> inference variable - for (typeParam : sigTypeRef.typeVars) { - typeParam2infVar.put(typeParam, infCtx.newInferenceVariable); - } - - processParameters(G, cache, infCtx, callExpr, sigTypeRef, typeParam2infVar); - - // create temporary type (i.e. may contain inference variables) - val resultTypeRefRaw = sigTypeRef.getReturnTypeRef(); - val resultTypeRef = resultTypeRefRaw.subst(G, typeParam2infVar); - - // register onSolved handlers to add final types to cache (i.e. may not contain inference variables) - infCtx.onSolved [ solution | handleOnSolved(G, cache, callExpr, resultTypeRef, typeParam2infVar, solution) ]; - - // return temporary type of callExpr (i.e. may contain inference variables) - return resultTypeRef; - } - - /** - * Processes all parameters and derives constraints from their bounds and matching types. - */ - private def void processParameters(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, - ParameterizedCallExpression callExpr, FunctionTypeExprOrRef fteor, Map typeParam2infVar - ) { - // - // (1) derive constraints from the bounds of the type parameters - // - val funcTypeVars = fteor.typeVars; - for (TypeVariable currTypeVar : funcTypeVars) { - // don't use currTypeVar.getDeclaredUpperBound() in next line! - val currUB = fteor.getTypeVarUpperBound(currTypeVar) ?: N4JSLanguageUtils.getTypeVariableImplicitUpperBound(G); - // constraint: currTypeVar <: current upper bound - val leftTypeRef = TypeUtils.createTypeRef(currTypeVar); - val leftTypeRefSubst = leftTypeRef.subst(G, typeParam2infVar); - val rightTypeRef = currUB; - val rightTypeRefSubst = rightTypeRef.subst(G, typeParam2infVar); - infCtx.addConstraint(leftTypeRefSubst, rightTypeRefSubst, Variance.CO); - } - - // - // (2) derive constraints from matching type of each provided argument to type of corresponding fpar - // - val int argsSize = callExpr.getArguments().size(); - for (var i = 0; i < argsSize; i++) { - val Expression arg = callExpr.getArguments().get(i)?.expression; - val TFormalParameter curr_fpar = fteor.getFparForArgIdx(i); - if (arg !== null && curr_fpar !== null) { - val fparTypeRef = curr_fpar.getTypeRef(); - val fparTypeRefSubst = fparTypeRef.subst(G, typeParam2infVar); - val TypeRef argType = polyProcessor.processExpr(G, arg, fparTypeRefSubst, infCtx, cache); - if (argType !== null) { - // constraint: argType <: fpar.type - infCtx.addConstraint(fparTypeRefSubst, argType, Variance.CONTRA); - // (note: no substitution in argType required, because it cannot contain any of the new inference - // variables introduced above) - } - } else if (arg !== null) { - // more arguments provided than fpars available - // -> this is an error case, but make sure to process the surplus arguments to avoid - // inconsistencies later on (cache misses etc.) - polyProcessor.processExpr(G, arg, null, infCtx, cache); - } - } - - // - // (3) derive constraints from matching expected return type to return type of function - // - // --> not required here (will be done by caller) - } - - /** - * Writes final types to cache. - */ - private def void handleOnSolved(RuleEnvironment G, ASTMetaInfoCache cache, ParameterizedCallExpression callExpr, - TypeRef resultTypeRef, Map typeParam2infVar, Optional> solution - ) { - if (solution.present) { - // success case: - cache.storeType(callExpr, resultTypeRef.applySolution(G, solution.get)); - val inferredTypeArgs = typeParam2infVar.values.map[solution.get.get(it)].toList; - cache.storeInferredTypeArgs(callExpr, inferredTypeArgs); - } else { - // failure case (unsolvable constraint system) - // to avoid leaking inference variables, replace them by their original type parameter - val fakeSolution = newHashMap; - for (e : typeParam2infVar.entrySet) { - fakeSolution.put(e.value, TypeUtils.createTypeRef(e.key)); - } - cache.storeType(callExpr, resultTypeRef.applySolution(G, fakeSolution)); - cache.storeInferredTypeArgs(callExpr, #[]); - } - // PolyProcessor#isResponsibleFor(TypableElement) claims responsibility of AST nodes of type 'Argument' - // contained in a ParameterizedCallExpression which is poly, so we are responsible for storing the types of - // those 'Argument' nodes in cache - // (note: compare this with similar handling of 'ArrayElement' nodes in PolyProcessor_ArrayLiteral) - for (arg : callExpr.arguments) { - val expr = arg?.expression; - val exprType = if (expr!==null) getFinalResultTypeOfNestedPolyExpression(expr); - if (exprType!==null) { - cache.storeType(arg, exprType); - } else { - cache.storeType(arg, TypeRefsFactory.eINSTANCE.createUnknownTypeRef()); - } - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_FunctionExpression.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_FunctionExpression.java new file mode 100644 index 0000000000..e0c7caff77 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_FunctionExpression.java @@ -0,0 +1,457 @@ +/** + * 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 com.google.common.collect.Iterators.concat; +import static com.google.common.collect.Iterators.singletonIterator; +import static java.util.Collections.emptyList; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.addFixedCapture; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.addTypeMappings; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.anyTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.getBuiltInTypeScope; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.getContextResource; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.getPredefinedTypes; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.newRuleEnvironment; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.undefinedTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.voidTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.wrap; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.toIterable; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.n4js.n4JS.ArrowFunction; +import org.eclipse.n4js.n4JS.Block; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.n4JS.FunctionExpression; +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef; +import org.eclipse.n4js.ts.typeRefs.ExistentialTypeRef; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExprOrRef; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression; +import org.eclipse.n4js.ts.typeRefs.OptionalFieldStrategy; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory; +import org.eclipse.n4js.ts.types.ContainerType; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.InferenceVariable; +import org.eclipse.n4js.ts.types.SyntaxRelatedTElement; +import org.eclipse.n4js.ts.types.TFormalParameter; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.ts.types.TypesPackage; +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.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper; +import org.eclipse.n4js.utils.EcoreUtilN4; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.xtext.xbase.lib.IterableExtensions; + +import com.google.common.base.Optional; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * {@link PolyProcessor} delegates here for processing array literals. + * + * @see PolyProcessor#inferType(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,ASTMetaInfoCache) + * @see PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache) + */ +@Singleton +class PolyProcessor_FunctionExpression extends AbstractPolyProcessor { + @Inject + private N4JSTypeSystem ts; + @Inject + private TypeSystemHelper tsh; + @Inject + private ASTProcessor astProcessor; + + /** + * BEFORE CHANGING THIS METHOD, READ THIS: + * {@link PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache)} + */ + TypeRef processFunctionExpression(RuleEnvironment G, FunctionExpression funExpr, TypeRef expectedTypeRef, + InferenceContext infCtx, ASTMetaInfoCache cache) { + TFunction fun = (TFunction) funExpr.getDefinedType(); // types builder will have created this already + + if (!isPoly(funExpr)) { // funExpr has declared types on all fpars and explicitly declared return type + // can't use xsemantics here, because it would give us a DeferredTypeRef + // return ts.type(G, funExpr).getValue(); + FunctionTypeExpression funTE = TypeUtils.createFunctionTypeExpression(null, emptyList(), fun.getFpars(), + fun.getReturnTypeRef()); // FIXME support for declared this type!! + // do not store in cache (TypeProcessor responsible for storing types of non-poly expressions in cache) + return funTE; + } + + // prepare temporary result type reference + FunctionTypeExpression funTE = TypeRefsFactory.eINSTANCE.createFunctionTypeExpression(); + + if (fun.getDeclaredThisType() != null) { + funTE.setDeclaredThisType(TypeUtils.copy(fun.getDeclaredThisType())); + } + + if (!fun.getTypeVars().isEmpty()) { + funTE.getOwnedTypeVars().addAll(toList(map(fun.getTypeVars(), tv -> TypeUtils.copy(tv)))); + } + + processFormalParameters(G, cache, infCtx, funExpr, funTE, expectedTypeRef); + processReturnType(G, cache, infCtx, funExpr, funTE); + + funTE.setReturnValueMarkedOptional(expectedTypeRef instanceof FunctionTypeExprOrRef + && ((FunctionTypeExprOrRef) expectedTypeRef).isReturnValueOptional()); + + // create temporary type (i.e. may contain inference variables) + + FunctionTypeExpression resultTypeRef; + if (fun.getTypeVars().isEmpty()) { + resultTypeRef = funTE; + } else { + // if fun is generic, we have to replace the type variables of fun by those of result1 + RuleEnvironment Gx = newRuleEnvironment(G); + addTypeMappings(Gx, fun.getTypeVars(), + toList(map(funTE.getOwnedTypeVars(), tv -> TypeUtils.createTypeRef(tv)))); + resultTypeRef = (FunctionTypeExpression) ts.substTypeVariables(Gx, funTE); + } + + // register onSolved handlers to add final types to cache (i.e. may not contain inference variables) + infCtx.onSolved( + solution -> handleOnSolved(G, cache, infCtx, funExpr, expectedTypeRef, resultTypeRef, solution)); + + // return temporary type of funExpr (i.e. may contain inference variables) + return resultTypeRef; + } + + /** + * Process formal parameters and also introduce inference variables for types of fpars, where needed. + */ + private void processFormalParameters(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, + FunctionExpression funExpr, FunctionTypeExpression funTE, TypeRef expectedTypeRef) { + TFunction fun = (TFunction) funExpr.getDefinedType(); // types builder will have created this already + FunctionTypeExprOrRef expectedFunctionTypeExprOrRef = (expectedTypeRef instanceof FunctionTypeExprOrRef) + ? (FunctionTypeExprOrRef) expectedTypeRef + : null; + + // first, new type refs for each formal parameter are created + int len = Math.min(funExpr.getFpars().size(), fun.getFpars().size()); + Map typeRefMap = new HashMap<>(); + for (var i = 0; i < len; i++) { + FormalParameter fparAST = funExpr.getFpars().get(i); + TFormalParameter fparT = fun.getFpars().get(i); + // use the TFormalParameter created by the types builder as a basis + TFormalParameter fparTCopy = TypeUtils.copy(fparT); + funTE.getFpars().add(fparTCopy); + typeRefMap.put(fparAST, fparTCopy); + + if (fparAST.getDeclaredTypeRef() == null) { + assertTrueIfRigid(cache, "type of formal parameter in TModule should be a DeferredTypeRef", + fparTCopy.getTypeRef() instanceof DeferredTypeRef); + + // Deferred type refs have to be resolved here + InferenceVariable iv = infCtx.newInferenceVariable(); + fparTCopy.setTypeRef(TypeUtils.createTypeRef(iv)); // <-- set new inference variable as type + } + } + + // Now, go through the map and check for deferred types. + // If any, include them into the constraint problem. + for (Map.Entry fparPair : typeRefMap.entrySet()) { + FormalParameter fparAST = fparPair.getKey(); + TFormalParameter fparTCopy = fparPair.getValue(); + if (fparAST.getDeclaredTypeRef() == null) { + InferenceVariable iv = (InferenceVariable) fparTCopy.getTypeRef().getDeclaredType(); + addConstraintForDefaultInitializers(fparAST, fparTCopy, G, cache, iv, infCtx, typeRefMap); + inferOptionalityFromExpectedFpar(funExpr, funTE, expectedFunctionTypeExprOrRef, fparAST, fparTCopy); + } + } + } + + /** + * When a function expression contains an initializer (in a default parameter), the type of this initializer is + * taken into account when calculating the parameter's type. + */ + private void addConstraintForDefaultInitializers(FormalParameter fparAST, TFormalParameter fparT, + RuleEnvironment G, ASTMetaInfoCache cache, InferenceVariable iv, InferenceContext infCtx, + Map typeRefMap) { + + if (fparAST.isHasInitializerAssignment()) { + // Check if the initializer refers to other fpars + + Expression fparInitializer = fparAST.getInitializer(); + TFormalParameter referredFparCopy = null; + boolean isPostponed = cache.postponedSubTrees.contains(fparInitializer); + if (fparInitializer instanceof IdentifierRef) { + IdentifierRef idRef = (IdentifierRef) fparInitializer; + IdentifiableElement id = idRef.getId(); + Object idInAST = (id instanceof SyntaxRelatedTElement) + ? id.eGet(TypesPackage.Literals.SYNTAX_RELATED_TELEMENT__AST_ELEMENT, false) + : null; + referredFparCopy = typeRefMap.get(idInAST); + } + + if (referredFparCopy != null) { + // example: f(a, b = a) {} + TypeRef tRef = referredFparCopy.getTypeRef(); // point to the inference variable introduced above + infCtx.addConstraint(TypeUtils.createTypeRef(iv), TypeUtils.copy(tRef), Variance.CONTRA); + } else if (!isPostponed) { + TypeRef context = (fparT.eContainer() instanceof ContainerType) + ? TypeUtils.createTypeRef((ContainerType) fparT.eContainer()) + : null; + RuleEnvironment G_withContext = ts.createRuleEnvironmentForContext(context, getContextResource(G)); + TypeRef iniTypeRef = (fparInitializer != null) ? ts.type(G_withContext, fparInitializer) + : undefinedTypeRef(G); + TypeRef iniTypeRefSubst = ts.substTypeVariables(G_withContext, iniTypeRef); + infCtx.addConstraint(TypeUtils.createTypeRef(iv), TypeUtils.copy(iniTypeRefSubst), Variance.CONTRA); + } + } + } + + /** + * if the corresponding fpar in the type expectation is optional, we make the fpar in the function expression + * optional as well Example: let fun: {function(string=)} = function(p) {}; + */ + private void inferOptionalityFromExpectedFpar(FunctionExpression funExpr, FunctionTypeExpression funTE, + FunctionTypeExprOrRef expectedFunctionTypeExprOrRef, FormalParameter fparAST, TFormalParameter fparTCopy) { + if (expectedFunctionTypeExprOrRef != null) { + int fparIdx = funExpr.getFpars().indexOf(fparAST); + TFormalParameter fparExpected = expectedFunctionTypeExprOrRef.getFparForArgIdx(fparIdx); + if (fparExpected != null && fparExpected.isOptional() && !fparExpected.isVariadic()) { + IterableExtensions.last(funTE.getFpars()).setHasInitializerAssignment(true); + EcoreUtilN4.doWithDeliver(false, () -> { + fparAST.setHasInitializerAssignment(true); + fparTCopy.setHasInitializerAssignment(true); + }, fparAST, fparTCopy); + } + } + } + + /** + * Processes return type (also introduce inference variable for return type, if needed) + */ + private void processReturnType(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, + FunctionExpression funExpr, FunctionTypeExpression funTE) { + TFunction fun = (TFunction) funExpr.getDefinedType(); // types builder will have created this already + TypeRef returnTypeRef; + if (funExpr.getDeclaredReturnTypeRef() != null) { + // explicitly declared return type + // -> take the type reference created by the types builder (but wrap in Promise if required) + returnTypeRef = TypeUtils.copy(fun.getReturnTypeRef()); + } else { + // undeclared return type + // -> create infVar for return type IFF funExpr contains return statements OR is single expr arrow function; + // otherwise use VOID as return type + assertTrueIfRigid(cache, "return type of TFunction in TModule should be a DeferredTypeRef", + fun.getReturnTypeRef() instanceof DeferredTypeRef); + + if (isReturningValue(funExpr)) { + // introduce new inference variable for (inner) return type + InferenceVariable iv = infCtx.newInferenceVariable(); + returnTypeRef = TypeUtils.createTypeRef(iv); + } else { + // void + returnTypeRef = voidTypeRef(G); + } + // to obtain outer return type: wrap in Promise if asynchronous and not Promise already + // for the time being, see N4JS Specification, Section 6.4.1 "Asynchronous Functions") + returnTypeRef = N4JSLanguageUtils.makePromiseIfAsync(funExpr, returnTypeRef, getBuiltInTypeScope(G)); + // to obtain outer return type: wrap in Generator if it is a generator function + // see N4JS Specification, Section 6.3.1 "Generator Functions") + returnTypeRef = N4JSLanguageUtils.makeGeneratorIfGeneratorFunction(funExpr, returnTypeRef, + getBuiltInTypeScope(G)); + } + funTE.setReturnTypeRef(returnTypeRef); + } + + /** + * Writes final types to cache + */ + private void handleOnSolved(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, + FunctionExpression funExpr, + TypeRef expectedTypeRef, FunctionTypeExpression resultTypeRef, + Optional> solution) { + Map solution2 = (solution.isPresent()) ? solution.get() + : createPseudoSolution(infCtx, anyTypeRef(G)); + // sanitize parameter types + // (but do not turn closed ExistentialTypeRefs into their upper bound that also appear in the expected type, + // by defining them as "fixed captures" via RuleEnvironmentExtensions#addFixedCapture()) + RuleEnvironment G2 = wrap(G); + Type returnTypeInfVar = resultTypeRef.getReturnTypeRef() == null ? null + : resultTypeRef.getReturnTypeRef().getDeclaredType(); // this infVar's solution must not be sanitized as + // it's not a parameter + TypeRef expectedTypeRefSubst = applySolution(expectedTypeRef, G, solution2); + if (expectedTypeRefSubst != null) { + Iterable capturesInExpectedTypeRef = filter(filter(toIterable( + concat(singletonIterator(expectedTypeRefSubst), expectedTypeRefSubst.eAllContents())), + ExistentialTypeRef.class), + etr -> !etr.isReopened()); + + for (ExistentialTypeRef it : capturesInExpectedTypeRef) { + addFixedCapture(G2, it); + } + } + Map solution3 = new HashMap<>(solution2); + boolean resolveLiteralTypes = false; // if we resolved here, we might break constraints (it's the responsibility + // of the constraint solver to avoid literal types as far as possible) + solution3.replaceAll((k, v) -> (k != returnTypeInfVar) + ? tsh.sanitizeTypeOfVariableFieldPropertyParameter(G2, v, resolveLiteralTypes) + : v); + // apply solution to resultTypeRef + FunctionTypeExprOrRef resultSolved0 = (FunctionTypeExprOrRef) applySolution(resultTypeRef, G, solution3); + if (resultSolved0 instanceof FunctionTypeExpression) { + resultSolved0 = TypeUtils.copy(resultSolved0); + ((FunctionTypeExpression) resultSolved0) + .setASTNodeOptionalFieldStrategy(OptionalFieldStrategy.FIELDS_AND_ACCESSORS_OPTIONAL); + } + FunctionTypeExprOrRef resultSolved = resultSolved0; + // store type of funExpr in cache ... + cache.storeType(funExpr, resultSolved); + // update the defined function in the TModule + TFunction fun = (TFunction) funExpr.getDefinedType(); // types builder will have created this already + replaceDeferredTypeRefs(fun, resultSolved); + if (fun.isReturnValueMarkedOptional() != resultSolved.isReturnValueOptional()) { + EcoreUtilN4.doWithDeliver(false, + () -> fun.setReturnValueMarkedOptional(resultSolved.isReturnValueOptional()), fun); + } + // store types of fpars in cache ... + int len = Math.min(funExpr.getFpars().size(), fun.getFpars().size()); + for (var i = 0; i < len; i++) { + FormalParameter fparAST = funExpr.getFpars().get(i); + TFormalParameter fparT = fun.getFpars().get(i); + TypeRef fparTw = TypeUtils.wrapIfVariadic(getPredefinedTypes(G).builtInTypeScope, fparT.getTypeRef(), + fparAST); + cache.storeType(fparAST, fparTw); + } + // tweak return type + if (funExpr instanceof ArrowFunction) { + log(0, "==START of special handling of single-expression arrow function"); + // NOTE: the next line requires the type of 'funExpr' and types of fpars to be in cache! For example: + // function foo(p: {function(int):T}) {return undefined;} + // foo( (i) => [i] ); + tweakReturnTypeOfSingleExpressionArrowFunction(G, cache, (ArrowFunction) funExpr, resultSolved); + log(0, "==END of special handling of single-expression arrow function"); + } + } + + /** + * Handling of a very specific special case of single-expression arrow functions. + *

+ * If the given arrow function is a single-expression arrow function, this method changes the return type of the + * given function type reference from non-void to void if the non-void return type would lead to a type error later + * on (for details see code of this method). + *

+ * This tweak is only required because our poor man's return type inferencer in the types builder infers a wrong + * non-void return type in some cases, which is corrected in this method. + *

+ * Example: + * + *

+	 * function foo(f : {function(): void}) {}
+	 * function none(): void {}
+	 * foo(() => none()); // will show bogus error when disabling this method
+	 * 
+ */ + private void tweakReturnTypeOfSingleExpressionArrowFunction(RuleEnvironment G, ASTMetaInfoCache cache, + ArrowFunction arrFun, FunctionTypeExprOrRef arrFunTypeRef) { + if (!arrFun.isSingleExprImplicitReturn()) { + return; // not applicable + } + // Step 1) process arrFun's body, which was postponed earlier according to ASTProcessor#isPostponedNode(EObject) + // Rationale: the body of a single-expression arrow function isn't a true block, so we do not have to + // postpone it AND we need its types in the next step. + Block block = arrFun.getBody(); + if (block == null) { + return; // broken AST + } + if (!cache.postponedSubTrees.remove(block)) { + throw new IllegalStateException( + "body of single-expression arrow function not among postponed subtrees, in resource: " + + arrFun.eResource().getURI()); + } + astProcessor.processSubtree(G, block, cache, 1); + // Step 2) adjust arrFun's return type stored in arrFunTypeRef (if required) + boolean didTweakReturnType = false; + Expression expr = arrFun.getSingleExpression(); + if (expr == null) { + return; // broken AST + } + TypeRef exprTypeRef = cache.getType(G, expr); // must now be in cache, because we just processed arrFun's body + if (TypeUtils.isVoid(exprTypeRef)) { + // the actual type of 'expr' is void + if (arrFunTypeRef instanceof FunctionTypeExpression) { + if (!TypeUtils.isVoid(arrFunTypeRef.getReturnTypeRef())) { + // the return type of the single-expression arrow function 'arrFun' is *not* void + // --> this would lead to a type error in N4JSTypeValidation, which we want to fix now + // in case the outer type expectation for the containing arrow function has a + // return type of 'void' OR there is no outer type expectation at all + FunctionTypeExprOrRef outerTypeExpectation = expectedTypeForArrowFunction(G, arrFun); + TypeRef outerReturnTypeExpectation = outerTypeExpectation == null ? null + : outerTypeExpectation.getReturnTypeRef(); + if (outerTypeExpectation == null + || (outerReturnTypeExpectation != null && TypeUtils.isVoid(outerReturnTypeExpectation))) { + // fix the future type error by changing the return type of the containing arrow function + // from non-void to void + if (isDEBUG_LOG()) { + log(1, "tweaking return type from " + (arrFunTypeRef.getReturnTypeRef() == null ? null + : arrFunTypeRef.getReturnTypeRef().getTypeRefAsString()) + " to void"); + } + EcoreUtilN4.doWithDeliver(false, + () -> ((FunctionTypeExpression) arrFunTypeRef).setReturnTypeRef(voidTypeRef(G)), + arrFunTypeRef); + if (isDEBUG_LOG()) { + log(1, "tweaked type of arrow function is: " + arrFunTypeRef.getTypeRefAsString()); + } + didTweakReturnType = true; + } + } + } + } + if (!didTweakReturnType) { + log(1, "tweaking of return type not required"); + } + } + + /** + * Replaces all DeferredTypeRefs in the given TFunction (i.e. in the fpars' types and the return type) by the + * corresponding types in 'result'. Argument 'result' must not contain any DeferredTypeRefs and, when this method + * returns, also the given TFunction 'fun' won't contain DeferredTypeRefs anymore. Will throw exception if 'fun' and + * 'result' do not match (e.g. 'result' has fewer fpars than 'fun'). + */ + private void replaceDeferredTypeRefs(TFunction fun, FunctionTypeExprOrRef result) { + int len = fun.getFpars().size(); // note: we do not take Math.min here (fail fast) + for (var i = 0; i < len; i++) { + TFormalParameter funFpar = fun.getFpars().get(i); + if (funFpar.getTypeRef() instanceof DeferredTypeRef) { + int idx = i; + EcoreUtilN4.doWithDeliver(false, + () -> funFpar.setTypeRef(TypeUtils.copy(result.getFpars().get(idx).getTypeRef())), funFpar); + } + } + if (fun.getReturnTypeRef() instanceof DeferredTypeRef) { + EcoreUtilN4.doWithDeliver(false, () -> fun.setReturnTypeRef(TypeUtils.copy(result.getReturnTypeRef())), + fun); + } + } + + private FunctionTypeExprOrRef expectedTypeForArrowFunction(RuleEnvironment G, ArrowFunction fe) { + RuleEnvironment G_new = newRuleEnvironment(G); + TypeRef tr = ts.expectedType(G_new, fe.eContainer(), fe); + if (tr instanceof FunctionTypeExprOrRef) { + return (FunctionTypeExprOrRef) tr; + } + return null; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_FunctionExpression.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_FunctionExpression.xtend deleted file mode 100644 index 1bf4f57c3b..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_FunctionExpression.xtend +++ /dev/null @@ -1,413 +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.common.base.Optional -import com.google.common.collect.Iterators -import com.google.inject.Inject -import com.google.inject.Singleton -import java.util.HashMap -import java.util.Map -import org.eclipse.n4js.n4JS.ArrowFunction -import org.eclipse.n4js.n4JS.FormalParameter -import org.eclipse.n4js.n4JS.FunctionExpression -import org.eclipse.n4js.n4JS.IdentifierRef -import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef -import org.eclipse.n4js.ts.typeRefs.ExistentialTypeRef -import org.eclipse.n4js.ts.typeRefs.FunctionTypeExprOrRef -import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression -import org.eclipse.n4js.ts.typeRefs.OptionalFieldStrategy -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory -import org.eclipse.n4js.ts.types.ContainerType -import org.eclipse.n4js.ts.types.InferenceVariable -import org.eclipse.n4js.ts.types.SyntaxRelatedTElement -import org.eclipse.n4js.ts.types.TFormalParameter -import org.eclipse.n4js.ts.types.TFunction -import org.eclipse.n4js.ts.types.TypesPackage -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.utils.RuleEnvironment -import org.eclipse.n4js.typesystem.utils.TypeSystemHelper -import org.eclipse.n4js.utils.EcoreUtilN4 -import org.eclipse.n4js.utils.N4JSLanguageUtils - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * {@link PolyProcessor} delegates here for processing array literals. - * - * @see PolyProcessor#inferType(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,ASTMetaInfoCache) - * @see PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache) - */ -@Singleton -package class PolyProcessor_FunctionExpression extends AbstractPolyProcessor { - @Inject - private N4JSTypeSystem ts; - @Inject - private TypeSystemHelper tsh; - @Inject - private ASTProcessor astProcessor; - - - /** - * BEFORE CHANGING THIS METHOD, READ THIS: - * {@link PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache)} - */ - def package TypeRef processFunctionExpression(RuleEnvironment G, FunctionExpression funExpr, TypeRef expectedTypeRef, - InferenceContext infCtx, ASTMetaInfoCache cache - ) { - val fun = funExpr.definedType as TFunction; // types builder will have created this already - - if (!funExpr.isPoly) { // funExpr has declared types on all fpars and explicitly declared return type - // can't use xsemantics here, because it would give us a DeferredTypeRef - // return ts.type(G, funExpr).getValue(); - val funTE = TypeUtils.createFunctionTypeExpression(null, #[], fun.fpars, fun.returnTypeRef); // FIXME support for declared this type!! - // do not store in cache (TypeProcessor responsible for storing types of non-poly expressions in cache) - return funTE; - } - - // prepare temporary result type reference - val funTE = TypeRefsFactory.eINSTANCE.createFunctionTypeExpression; - - if (fun.declaredThisType !== null) { - funTE.declaredThisType = TypeUtils.copy(fun.declaredThisType); - } - - if (!fun.typeVars.empty) { - funTE.ownedTypeVars += fun.typeVars.map[tv|TypeUtils.copy(tv)]; - } - - processFormalParameters(G, cache, infCtx, funExpr, funTE, expectedTypeRef); - processReturnType(G, cache, infCtx, funExpr, funTE); - - funTE.returnValueMarkedOptional = expectedTypeRef instanceof FunctionTypeExprOrRef - && (expectedTypeRef as FunctionTypeExprOrRef).returnValueOptional; - - // create temporary type (i.e. may contain inference variables) - val resultTypeRef = if (fun.typeVars.empty) { - funTE - } else { - // if fun is generic, we have to replace the type variables of fun by those of result1 - val Gx = G.newRuleEnvironment; - Gx.addTypeMappings(fun.typeVars, funTE.ownedTypeVars.map[TypeUtils.createTypeRef(it)]); - ts.substTypeVariables(Gx, funTE) as FunctionTypeExpression; - }; - - // register onSolved handlers to add final types to cache (i.e. may not contain inference variables) - infCtx.onSolved [ solution | handleOnSolved(G, cache, infCtx, funExpr, expectedTypeRef, resultTypeRef, solution)]; - - // return temporary type of funExpr (i.e. may contain inference variables) - return resultTypeRef; - } - - /** - * Process formal parameters and also introduce inference variables for types of fpars, where needed. - */ - private def void processFormalParameters(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, - FunctionExpression funExpr, FunctionTypeExpression funTE, TypeRef expectedTypeRef - ) { - val fun = funExpr.definedType as TFunction; // types builder will have created this already - val expectedFunctionTypeExprOrRef = if (expectedTypeRef instanceof FunctionTypeExprOrRef) { expectedTypeRef }; - - // first, new type refs for each formal parameter are created - val len = Math.min(funExpr.fpars.size, fun.fpars.size); - val Map typeRefMap = new HashMap(); - for (var i = 0; i < len; i++) { - val fparAST = funExpr.fpars.get(i); - val fparT = fun.fpars.get(i); - val fparTCopy = TypeUtils.copy(fparT); // use the TFormalParameter created by the types builder as a basis - funTE.fpars += fparTCopy; - typeRefMap.put(fparAST, fparTCopy); - - if (fparAST.declaredTypeRef === null) { - assertTrueIfRigid(cache, "type of formal parameter in TModule should be a DeferredTypeRef", - fparTCopy?.typeRef instanceof DeferredTypeRef); - - // Deferred type refs have to be resolved here - val iv = infCtx.newInferenceVariable; - fparTCopy.typeRef = TypeUtils.createTypeRef(iv); // <-- set new inference variable as type - } - } - - // Now, go through the map and check for deferred types. - // If any, include them into the constraint problem. - for (Map.Entry fparPair : typeRefMap.entrySet) { - val fparAST = fparPair.key; - val fparTCopy = fparPair.value; - if (fparAST.declaredTypeRef === null) { - val iv = fparTCopy.typeRef.declaredType as InferenceVariable; - addConstraintForDefaultInitializers(funExpr, fparAST, fparTCopy, G, cache, iv, infCtx, typeRefMap); - inferOptionalityFromExpectedFpar(funExpr, funTE, expectedFunctionTypeExprOrRef, fparAST, fparTCopy); - } - } - } - - /** - * When a function expression contains an initializer (in a default parameter), - * the type of this initializer is taken into account when calculating the parameter's type. - */ - private def void addConstraintForDefaultInitializers(FunctionExpression funExpr, FormalParameter fparAST, TFormalParameter fparT, - RuleEnvironment G, ASTMetaInfoCache cache, InferenceVariable iv, InferenceContext infCtx, Map typeRefMap - ) { - if (fparAST.hasInitializerAssignment) { - // Check if the initializer refers to other fpars - - val fparInitializer = fparAST.initializer; - var referredFparCopy = null as TFormalParameter; - val isPostponed = cache.postponedSubTrees.contains(fparInitializer); - if (fparInitializer instanceof IdentifierRef) { - val id = fparInitializer.getId(); - val idInAST = if (id instanceof SyntaxRelatedTElement) id.eGet(TypesPackage.Literals.SYNTAX_RELATED_TELEMENT__AST_ELEMENT, false); - referredFparCopy = typeRefMap.get(idInAST); - } - - if (referredFparCopy !== null) { - // example: f(a, b = a) {} - val TypeRef tRef = referredFparCopy.typeRef; // point to the inference variable introduced above - infCtx.addConstraint(TypeUtils.createTypeRef(iv), TypeUtils.copy(tRef), Variance.CONTRA); - } else if (!isPostponed) { - val context = if (fparT.eContainer instanceof ContainerType) - TypeUtils.createTypeRef(fparT.eContainer as ContainerType) else null; - val G_withContext = ts.createRuleEnvironmentForContext(context, G.contextResource); - val TypeRef iniTypeRef = if (fparInitializer !== null) ts.type(G_withContext, fparInitializer) else G.undefinedTypeRef; - val iniTypeRefSubst = ts.substTypeVariables(G_withContext, iniTypeRef); - infCtx.addConstraint(TypeUtils.createTypeRef(iv), TypeUtils.copy(iniTypeRefSubst), Variance.CONTRA); - } - } - } - - /** - * if the corresponding fpar in the type expectation is optional, we make the fpar in the - * function expression optional as well - * Example: - * let fun: {function(string=)} = function(p) {}; - */ - private def void inferOptionalityFromExpectedFpar(FunctionExpression funExpr, FunctionTypeExpression funTE, - FunctionTypeExprOrRef expectedFunctionTypeExprOrRef, FormalParameter fparAST, TFormalParameter fparTCopy - ) { - if (expectedFunctionTypeExprOrRef !== null) { - val int fparIdx = funExpr.fpars.indexOf(fparAST); - val fparExpected = expectedFunctionTypeExprOrRef.getFparForArgIdx(fparIdx); - if (fparExpected !== null && fparExpected.optional && !fparExpected.variadic) { - funTE.fpars.last.hasInitializerAssignment = true; - EcoreUtilN4.doWithDeliver(false, [ - fparAST.hasInitializerAssignment = true; - fparTCopy.hasInitializerAssignment = true; - ], fparAST, fparTCopy); - } - } - } - - /** - * Processes return type (also introduce inference variable for return type, if needed) - */ - private def void processReturnType(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, - FunctionExpression funExpr, FunctionTypeExpression funTE - ) { - val fun = funExpr.definedType as TFunction; // types builder will have created this already - var TypeRef returnTypeRef; - if (funExpr.declaredReturnTypeRef !== null) { - // explicitly declared return type - // -> take the type reference created by the types builder (but wrap in Promise if required) - returnTypeRef = TypeUtils.copy(fun.returnTypeRef); - } else { - // undeclared return type - // -> create infVar for return type IFF funExpr contains return statements OR is single expr arrow function; otherwise use VOID as return type - assertTrueIfRigid(cache, "return type of TFunction in TModule should be a DeferredTypeRef", - fun.returnTypeRef instanceof DeferredTypeRef); - - if (funExpr.isReturningValue) { - // introduce new inference variable for (inner) return type - val iv = infCtx.newInferenceVariable; - returnTypeRef = TypeUtils.createTypeRef(iv); - } else { - // void - returnTypeRef = G.voidTypeRef; - } - // to obtain outer return type: wrap in Promise if asynchronous and not Promise already - // for the time being, see N4JS Specification, Section 6.4.1 "Asynchronous Functions") - returnTypeRef = N4JSLanguageUtils.makePromiseIfAsync(funExpr, returnTypeRef, G.builtInTypeScope); - // to obtain outer return type: wrap in Generator if it is a generator function - // see N4JS Specification, Section 6.3.1 "Generator Functions") - returnTypeRef = N4JSLanguageUtils.makeGeneratorIfGeneratorFunction(funExpr, returnTypeRef, G.builtInTypeScope); - } - funTE.returnTypeRef = returnTypeRef; - } - - - /** - * Writes final types to cache - */ - private def void handleOnSolved(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, FunctionExpression funExpr, - TypeRef expectedTypeRef, FunctionTypeExpression resultTypeRef, Optional> solution - ) { - val solution2 = if (solution.present) solution.get else infCtx.createPseudoSolution(G.anyTypeRef); - // sanitize parameter types - // (but do not turn closed ExistentialTypeRefs into their upper bound that also appear in the expected type, - // by defining them as "fixed captures" via RuleEnvironmentExtensions#addFixedCapture()) - val G2 = G.wrap; - val returnTypeInfVar = resultTypeRef.returnTypeRef?.declaredType; // this infVar's solution must not be sanitized as it's not a parameter - val expectedTypeRefSubst = expectedTypeRef.applySolution(G, solution2); - if (expectedTypeRefSubst !== null) { - val capturesInExpectedTypeRef = Iterators.concat(Iterators.singletonIterator(expectedTypeRefSubst), expectedTypeRefSubst.eAllContents) - .filter(ExistentialTypeRef) - .filter[!reopened]; - capturesInExpectedTypeRef.forEach[G2.addFixedCapture(it)]; - } - val solution3 = new HashMap(solution2); - val resolveLiteralTypes = false; // if we resolved here, we might break constraints (it's the responsibility of the constraint solver to avoid literal types as far as possible) - solution3.replaceAll[k, v | if (k !== returnTypeInfVar) tsh.sanitizeTypeOfVariableFieldPropertyParameter(G2, v, resolveLiteralTypes) else v]; - // apply solution to resultTypeRef - var resultSolved0 = resultTypeRef.applySolution(G, solution3) as FunctionTypeExprOrRef; - if (resultSolved0 instanceof FunctionTypeExpression) { - resultSolved0 = TypeUtils.copy(resultSolved0); - (resultSolved0 as FunctionTypeExpression).ASTNodeOptionalFieldStrategy = OptionalFieldStrategy.FIELDS_AND_ACCESSORS_OPTIONAL; - } - val resultSolved = resultSolved0; - // store type of funExpr in cache ... - cache.storeType(funExpr, resultSolved); - // update the defined function in the TModule - val fun = funExpr.definedType as TFunction; // types builder will have created this already - fun.replaceDeferredTypeRefs(resultSolved); - if(fun.returnValueMarkedOptional !== resultSolved.returnValueOptional) { - EcoreUtilN4.doWithDeliver(false, [ - fun.returnValueMarkedOptional = resultSolved.returnValueOptional; - ], fun); - } - // store types of fpars in cache ... - val len = Math.min(funExpr.fpars.size, fun.fpars.size); - for (var i = 0; i < len; i++) { - val fparAST = funExpr.fpars.get(i); - val fparT = fun.fpars.get(i); - val fparTw = TypeUtils.wrapIfVariadic(G.getPredefinedTypes().builtInTypeScope, fparT.typeRef, fparAST); - cache.storeType(fparAST, fparTw); - } - // tweak return type - if (funExpr instanceof ArrowFunction) { - log(0, "===START of special handling of single-expression arrow function"); - // NOTE: the next line requires the type of 'funExpr' and types of fpars to be in cache! For example: - // function foo(p: {function(int):T}) {return undefined;} - // foo( (i) => [i] ); - tweakReturnTypeOfSingleExpressionArrowFunction(G, cache, funExpr, resultSolved); - log(0, "===END of special handling of single-expression arrow function"); - } - } - - /** - * Handling of a very specific special case of single-expression arrow functions. - *

- * If the given arrow function is a single-expression arrow function, this method changes the return type of the - * given function type reference from non-void to void if the non-void return type would lead to a type error later - * on (for details see code of this method). - *

- * This tweak is only required because our poor man's return type inferencer in the types builder infers a wrong - * non-void return type in some cases, which is corrected in this method. - *

- * Example: - *

-	 * function foo(f : {function(): void}) {}
-	 * function none(): void {}
-	 * foo(() => none()); // will show bogus error when disabling this method
-	 * 
- */ - def private void tweakReturnTypeOfSingleExpressionArrowFunction(RuleEnvironment G, ASTMetaInfoCache cache, - ArrowFunction arrFun, FunctionTypeExprOrRef arrFunTypeRef - ) { - if (!arrFun.isSingleExprImplicitReturn) { - return; // not applicable - } - // Step 1) process arrFun's body, which was postponed earlier according to ASTProcessor#isPostponedNode(EObject) - // Rationale: the body of a single-expression arrow function isn't a true block, so we do not have to - // postpone it AND we need its types in the next step. - val block = arrFun.body; - if (block === null) { - return; // broken AST - } - if(!cache.postponedSubTrees.remove(block)) { - throw new IllegalStateException("body of single-expression arrow function not among postponed subtrees, in resource: " + arrFun.eResource.URI); - } - astProcessor.processSubtree(G, block, cache, 1); - // Step 2) adjust arrFun's return type stored in arrFunTypeRef (if required) - var didTweakReturnType = false; - val expr = arrFun.getSingleExpression(); - if (expr === null) { - return; // broken AST - } - val exprTypeRef = cache.getType(G, expr); // must now be in cache, because we just processed arrFun's body - if (TypeUtils.isVoid(exprTypeRef)) { - // the actual type of 'expr' is void - if (arrFunTypeRef instanceof FunctionTypeExpression) { - if (!TypeUtils.isVoid(arrFunTypeRef.returnTypeRef)) { - // the return type of the single-expression arrow function 'arrFun' is *not* void - // --> this would lead to a type error in N4JSTypeValidation, which we want to fix now - // in case the outer type expectation for the containing arrow function has a - // return type of 'void' OR there is no outer type expectation at all - val outerTypeExpectation = expectedTypeForArrowFunction(G, arrFun); - val outerReturnTypeExpectation = outerTypeExpectation?.returnTypeRef; - if (outerTypeExpectation === null - || (outerReturnTypeExpectation !== null && TypeUtils.isVoid(outerReturnTypeExpectation))) { - // fix the future type error by changing the return type of the containing arrow function - // from non-void to void - if (isDEBUG_LOG) { - log(1, "tweaking return type from " + arrFunTypeRef.returnTypeRef?.typeRefAsString + " to void"); - } - EcoreUtilN4.doWithDeliver(false, [ - arrFunTypeRef.returnTypeRef = G.voidTypeRef; - ], arrFunTypeRef); - if (isDEBUG_LOG) { - log(1, "tweaked type of arrow function is: " + arrFunTypeRef.typeRefAsString); - } - didTweakReturnType = true; - } - } - } - } - if(!didTweakReturnType) { - log(1, "tweaking of return type not required"); - } - } - - /** - * Replaces all DeferredTypeRefs in the given TFunction (i.e. in the fpars' types and the return type) - * by the corresponding types in 'result'. Argument 'result' must not contain any DeferredTypeRefs and, - * when this method returns, also the given TFunction 'fun' won't contain DeferredTypeRefs anymore. - * Will throw exception if 'fun' and 'result' do not match (e.g. 'result' has fewer fpars than 'fun'). - */ - def private void replaceDeferredTypeRefs(TFunction fun, FunctionTypeExprOrRef result) { - val len = fun.fpars.length; // note: we do not take Math.min here (fail fast) - for (var i = 0; i < len; i++) { - val funFpar = fun.fpars.get(i); - if (funFpar.typeRef instanceof DeferredTypeRef) { - val idx = i; - EcoreUtilN4.doWithDeliver(false, [ - funFpar.typeRef = TypeUtils.copy(result.fpars.get(idx).typeRef); - ], funFpar); - } - } - if (fun.returnTypeRef instanceof DeferredTypeRef) { - EcoreUtilN4.doWithDeliver(false, [ - fun.returnTypeRef = TypeUtils.copy(result.returnTypeRef); - ], fun); - } - } - - def private FunctionTypeExprOrRef expectedTypeForArrowFunction(RuleEnvironment G, ArrowFunction fe) { - val G_new = G.newRuleEnvironment; - val tr = ts.expectedType(G_new, fe.eContainer(), fe); - if (tr instanceof FunctionTypeExprOrRef) { - return tr; - } - return null; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_ObjectLiteral.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_ObjectLiteral.java new file mode 100644 index 0000000000..e6d628825e --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_ObjectLiteral.java @@ -0,0 +1,407 @@ +/** + * 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.anyTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.anyTypeRefDynamic; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.objectType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.n4JS.N4JSASTUtils; +import org.eclipse.n4js.n4JS.ObjectLiteral; +import org.eclipse.n4js.n4JS.PropertyAssignment; +import org.eclipse.n4js.n4JS.PropertyGetterDeclaration; +import org.eclipse.n4js.n4JS.PropertyMethodDeclaration; +import org.eclipse.n4js.n4JS.PropertyNameValuePair; +import org.eclipse.n4js.n4JS.PropertySetterDeclaration; +import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef; +import org.eclipse.n4js.ts.typeRefs.LiteralTypeRef; +import org.eclipse.n4js.ts.typeRefs.OptionalFieldStrategy; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRefStructural; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.ContainerType; +import org.eclipse.n4js.ts.types.FieldAccessor; +import org.eclipse.n4js.ts.types.InferenceVariable; +import org.eclipse.n4js.ts.types.TGetter; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.ts.types.TStructGetter; +import org.eclipse.n4js.ts.types.TStructMember; +import org.eclipse.n4js.ts.types.TStructuralType; +import org.eclipse.n4js.ts.types.TypingStrategy; +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.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper; +import org.eclipse.n4js.utils.EcoreUtilN4; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.xtext.xbase.lib.Pair; + +import com.google.common.base.Optional; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * {@link PolyProcessor} delegates here for processing array literals. + * + * @see PolyProcessor#inferType(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,ASTMetaInfoCache) + * @see PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache) + */ +@Singleton +class PolyProcessor_ObjectLiteral extends AbstractPolyProcessor { + + @Inject + private PolyProcessor polyProcessor; + @Inject + private N4JSTypeSystem ts; + @Inject + private TypeSystemHelper tsh; + + /** + * BEFORE CHANGING THIS METHOD, READ THIS: + * {@link PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache)} + */ + TypeRef processObjectLiteral(RuleEnvironment G, ObjectLiteral objLit, TypeRef expectedTypeRef, + InferenceContext infCtx, ASTMetaInfoCache cache) { + + if (!isPoly(objLit)) { + TypeRef result = ts.type(G, objLit); + // do not store in cache (TypeProcessor responsible for storing types of non-poly expressions in cache) + return result; + } + + // quick mode as a performance tweak: + boolean haveUsableExpectedType = expectedTypeRef != null && expectedTypeRef.isStructuralTyping(); // TODO + // reconsider + boolean quickMode = !haveUsableExpectedType && !TypeUtils.isInferenceVariable(expectedTypeRef); + + List tMembers = new ArrayList<>(); + // in standard mode: the following list will contain pairs from property assignments to inference variables + // in quick mode: the following list will contain pairs from property assignments to fallback types + List> props2InfVarOrFallbackType = new ArrayList<>(); + processProperties(G, cache, infCtx, objLit, tMembers, quickMode, props2InfVarOrFallbackType); + linkGetterSetterPairs(infCtx, tMembers, quickMode); + + // create temporary type (i.e. may contain inference variables) + ParameterizedTypeRefStructural resultTypeRef = TypeUtils.createParameterizedTypeRefStructural(objectType(G), + TypingStrategy.STRUCTURAL, tMembers.toArray(new TStructMember[0])); + resultTypeRef.setASTNodeOptionalFieldStrategy(OptionalFieldStrategy.FIELDS_AND_ACCESSORS_OPTIONAL); + + // register onSolved handlers to add final types to cache (i.e. may not contain inference variables) + infCtx.onSolved( + solution -> handleOnSolved(G, cache, infCtx, objLit, quickMode, props2InfVarOrFallbackType, solution)); + + // return temporary type of objLit (i.e. may contain inference variables) + return resultTypeRef; + } + + /** + * Processes all properties of an object literal. As of now, methods are not processed. + */ + private void processProperties(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, + ObjectLiteral objLit, List tMembers, + boolean quickMode, List> props2InfVarOrFallbackType) { + for (PropertyAssignment pa : objLit.getPropertyAssignments()) { + if (pa != null) { + TStructMember tMember = null; + if (pa.getDefinedMember() != null) { + tMember = TypeUtils.copy(pa.getDefinedMember()); + tMembers.add(tMember); + } else { + // this happens if member name in AST was null (bad syntax in code; types builder won't + // create a TMember in that case) or in case of an invalid computed name (not a compile-time + // expression, unresolved reference, etc.; ComputedNameProcessor will remote TMember for + // consistency with first case); this is an error case, but we still have to continue + // processing, because nested expression must still be typed as usual. + } + + if (isPoly(pa)) { + if (!(tMember instanceof TMethod)) { + processNonMethodProperties(G, cache, infCtx, tMember, pa, + quickMode, props2InfVarOrFallbackType); + } + } + } + } + } + + /** + * For each property in the object literal: a) introduce a new inference variable representing the property's type + * (except for methods) b) add a constraint: expressionTypeRef <: iv c) create a TStructMember to be used in the + * structural result type reference + * + * @param tMember + * may be null in case of invalid members. + */ + private void processNonMethodProperties(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, + TStructMember tMember, PropertyAssignment propAssignm, + boolean quickMode, List> props2InfVarOrFallbackType) { + if (tMember != null) { + TypeRef originalMemberType = getTypeOfMember(tMember); + + assertTrueIfRigid(cache, + "type of " + propAssignm.eClass().getName() + " in TModule should be a DeferredTypeRef", + originalMemberType instanceof DeferredTypeRef); + + if (!(originalMemberType instanceof DeferredTypeRef)) { + return; // if rigid assertions are turned off, we fail safe (should never happen) + } + } + + if (!quickMode) { + // standard mode: + // create new inference variable for the to-be-inferred type of this property + InferenceVariable iv = infCtx.newInferenceVariable(); + // set it as type in tMember + if (tMember != null) { + setTypeOfMember(tMember, TypeUtils.createTypeRef(iv)); + } + // add a constraint for the initializer expression (if any) + if (propAssignm instanceof PropertyNameValuePair) { + PropertyNameValuePair pnvp = (PropertyNameValuePair) propAssignm; + if (pnvp.getExpression() != null) { + TypeRef exprTypeRef = polyProcessor.processExpr(G, pnvp.getExpression(), + TypeUtils.createTypeRef(iv), infCtx, cache); + infCtx.addConstraint(exprTypeRef, TypeUtils.createTypeRef(iv), Variance.CO); // exprTypeRef <: iv + } + } + // remember for later + props2InfVarOrFallbackType.add(Pair.of(propAssignm, iv)); + } else { + // quick mode: + // compute a fall-back type + TypeRef fallbackType = anyTypeRef(G); + if (propAssignm instanceof PropertyNameValuePair) { + PropertyNameValuePair pnvp = (PropertyNameValuePair) propAssignm; + if (pnvp.getExpression() != null) { + fallbackType = polyProcessor.processExpr(G, pnvp.getExpression(), null, infCtx, cache); + } + } else if (propAssignm instanceof PropertyGetterDeclaration) { + fallbackType = getDeclaredTypeOfOtherAccessorInPair((PropertyGetterDeclaration) propAssignm); + } else if (propAssignm instanceof PropertySetterDeclaration) { + fallbackType = getDeclaredTypeOfOtherAccessorInPair((PropertySetterDeclaration) propAssignm); + } + if (fallbackType == null) { + fallbackType = anyTypeRef(G); + } + + // set it as type in tMember + if (tMember != null) { + setTypeOfMember(tMember, TypeUtils.copy(fallbackType)); + } + // remember for later + props2InfVarOrFallbackType.add(Pair.of(propAssignm, fallbackType)); + } + } + + private TypeRef getDeclaredTypeOfOtherAccessorInPair(org.eclipse.n4js.n4JS.FieldAccessor accAST) { + FieldAccessor otherPair = findOtherAccessorInPair(accAST.getDefinedAccessor()); + org.eclipse.n4js.n4JS.FieldAccessor otherPairAST = otherPair == null ? null + : (org.eclipse.n4js.n4JS.FieldAccessor) otherPair.getAstElement(); + TypeRef declTypeRef = otherPairAST == null ? null : otherPairAST.getDeclaredTypeRef(); + return declTypeRef; + } + + private FieldAccessor findOtherAccessorInPair(FieldAccessor acc) { + if (acc != null && acc.getName() != null) { + EObject type = acc.eContainer(); + if (type instanceof ContainerType) { + boolean lookForWriteAccess = acc instanceof TGetter; + EObject result = ((ContainerType) type).findOwnedMember(acc.getName(), lookForWriteAccess, + acc.isStatic()); + if (result instanceof FieldAccessor) { + return (FieldAccessor) result; + } + } + } + return null; + } + + /** + * Add a constraint for each getter/setter pair reflecting the relation between the getter's and setter's type. + * Required to make the getter obtain its implicit type from the corresponding setter, and vice versa. + */ + private void linkGetterSetterPairs(InferenceContext infCtx, List tMembers, boolean quickMode) { + if (!quickMode) { // not in quick mode + for (TStructMember tMember : tMembers) { + if (tMember instanceof TStructGetter) { + TStructGetter tsg = (TStructGetter) tMember; + FieldAccessor tOtherInPair = findOtherAccessorInPair(tsg); + if (tOtherInPair != null) { + TypeRef typeGetter = getTypeOfMember(tsg); + TypeRef typeSetter = getTypeOfMember(tOtherInPair); + if (TypeUtils.isInferenceVariable(typeGetter) || TypeUtils.isInferenceVariable(typeSetter)) { + infCtx.addConstraint(typeGetter, typeSetter, Variance.CO); + } else { + // do not add a constraint if both types were explicitly declared + // (then this constraint does not apply!!) + } + } + } + } + } + } + + /** + * Writes final types to cache + */ + private void handleOnSolved(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, + ObjectLiteral objLit, + boolean quickMode, List> props2InfVarOrFallbackType, + Optional> solution) { + for (Pair propPair : props2InfVarOrFallbackType) { + PropertyAssignment propAssignm = propPair.getKey(); + TStructMember memberInTModule = propAssignm.getDefinedMember(); + if (memberInTModule != null) { + TypeRef memberType = getMemberType(G, infCtx, solution, quickMode, propPair); + boolean resolveLiteralTypes = (quickMode) + ? // quick mode means we do not have a type expectation, so we handle literal types exactly + // as when inferring the implicit type of variables with an initializer expression + !N4JSASTUtils.isImmutable(propAssignm) + : + // standard mode means we have a type expectation; if we resolved here, we might break + // constraints (instead, it's the responsibility of the constraint solver to avoid literal + // types as far as possible) + false; + TypeRef memberTypeSane = tsh.sanitizeTypeOfVariableFieldPropertyParameter(G, memberType, + resolveLiteralTypes); + EcoreUtilN4.doWithDeliver(false, () -> setTypeOfMember(memberInTModule, TypeUtils.copy(memberTypeSane)), + memberInTModule); + } + } + + ParameterizedTypeRefStructural resultFinal = TypeUtils.createParameterizedTypeRefStructural(objectType(G), + TypingStrategy.STRUCTURAL, + (TStructuralType) objLit.getDefinedType()); + resultFinal.setASTNodeOptionalFieldStrategy(OptionalFieldStrategy.FIELDS_AND_ACCESSORS_OPTIONAL); + cache.storeType(objLit, resultFinal); + + for (PropertyAssignment currAss : objLit.getPropertyAssignments()) { + if (currAss.getDefinedMember() != null) { + if (currAss instanceof PropertyMethodDeclaration) { + cache.storeType(currAss, + TypeUtils.createTypeRef(((PropertyMethodDeclaration) currAss).getDefinedMember())); + } else { + cache.storeType(currAss, TypeUtils.copy(getTypeOfMember(currAss.getDefinedMember()))); + } + } else { + if (currAss instanceof PropertyNameValuePair + && ((PropertyNameValuePair) currAss).getExpression() != null) { + TypeRef exprType = ts.type(G, ((PropertyNameValuePair) currAss).getExpression()); + cache.storeType(currAss, exprType); + } else { + cache.storeType(currAss, anyTypeRefDynamic(G)); + } + } + } + } + + private TypeRef getMemberType(RuleEnvironment G, InferenceContext infCtx, + Optional> solution, + boolean quickMode, Pair prop2InfVarOrFallbackType) { + + TypeRef memberType = null; + PropertyAssignment propAssignm = prop2InfVarOrFallbackType.getKey(); + if (solution.isPresent()) { + if (quickMode) { + // success case (quick mode): + TypeRef fallbackType = (TypeRef) prop2InfVarOrFallbackType.getValue(); // value is a TypeRef + if (propAssignm instanceof PropertyNameValuePair) { + memberType = getFinalResultTypeOfNestedPolyExpression( + ((PropertyNameValuePair) propAssignm).getExpression()); + } else { + memberType = applySolution(TypeUtils.copy(fallbackType), G, solution.get()); + } + + } else { + // success case (standard mode): + InferenceVariable infVar = (InferenceVariable) prop2InfVarOrFallbackType.getValue(); // value is an + // infVar + TypeRef fromSolution = solution.get().get(infVar); + + if (propAssignm instanceof PropertyNameValuePair) { + TypeRef fromCache = (((PropertyNameValuePair) propAssignm).getExpression() instanceof ObjectLiteral) + ? getFinalResultTypeOfNestedPolyExpression( + ((PropertyNameValuePair) propAssignm).getExpression()) + : null; + if (fromCache != null && ts.equaltypeSucceeded(G, fromCache, fromSolution)) { + // tweak for nested ObjectLiterals in initializer expression of PropertyNameValuePairs: + // the solution from the infCtx will be a StructuralTypeRef with 'genStructuralMembers' + // but the result of the nested poly computation (via the cache) will give us a much more + // efficient StructuralTypeRef with 'structuralType' pointing to the TStructuralType in the + // TModule + memberType = fromCache; + } else { + memberType = fromSolution; + } + } else { + memberType = fromSolution; + } + } + } else { + // failure case (both modes): + if (propAssignm instanceof PropertyNameValuePair) { + memberType = getFinalResultTypeOfNestedPolyExpression( + ((PropertyNameValuePair) propAssignm).getExpression()); + memberType = adjustMemberTypeToAvoidMisleadingLiteralTypes(G, infCtx, quickMode, + prop2InfVarOrFallbackType, memberType); + } else { + memberType = anyTypeRef(G); + } + } + + return memberType; + } + + /** + * Replaces literal types by their base type to avoid confusing error messages. + *

+ * Without this tweak, the following code + * + *

+	 * let x: ~Object with { prop1: string, prop2: number } = { prop1: "hello", prop2: "BAD!" };
+	 * 
+ * + * would produce the misleading error message + * + *
+	 * ~Object with { prop1: "hello"; prop2: "BAD!" } is not a structural subtype of ~Object with { prop1: string; prop2: number }: prop1 failed: "hello" is not equal to string and 1 more problems.
+	 * 
+ * + * With this tweak it changes to: + * + *
+	 * ~Object with { prop1: string; prop2: "BAD!" } is not a structural subtype of ~Object with { prop1: string; prop2: number }: prop2 failed: "BAD!" is not equal to number.
+	 * 
+ */ + private TypeRef adjustMemberTypeToAvoidMisleadingLiteralTypes(RuleEnvironment G, InferenceContext infCtx, + boolean quickMode, Pair prop2InfVarOrFallbackType, + TypeRef memberType) { + if (!quickMode) { + if (memberType instanceof LiteralTypeRef) { + // value is an infVar + InferenceVariable infVar = (InferenceVariable) prop2InfVarOrFallbackType.getValue(); + if (!infCtx.isPromisingPartialSolution(infVar, memberType)) { + TypeRef baseType = N4JSLanguageUtils.getLiteralTypeBase(G, memberType); + if (infCtx.isPromisingPartialSolution(infVar, baseType)) { + return baseType; + } + } + } + } + return memberType; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_ObjectLiteral.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_ObjectLiteral.xtend deleted file mode 100644 index ebb4ac0fc4..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/PolyProcessor_ObjectLiteral.xtend +++ /dev/null @@ -1,378 +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.common.base.Optional -import com.google.inject.Inject -import com.google.inject.Singleton -import java.util.List -import java.util.Map -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.n4JS.N4JSASTUtils -import org.eclipse.n4js.n4JS.ObjectLiteral -import org.eclipse.n4js.n4JS.PropertyAssignment -import org.eclipse.n4js.n4JS.PropertyGetterDeclaration -import org.eclipse.n4js.n4JS.PropertyMethodDeclaration -import org.eclipse.n4js.n4JS.PropertyNameValuePair -import org.eclipse.n4js.n4JS.PropertySetterDeclaration -import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef -import org.eclipse.n4js.ts.typeRefs.LiteralTypeRef -import org.eclipse.n4js.ts.typeRefs.OptionalFieldStrategy -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.ContainerType -import org.eclipse.n4js.ts.types.FieldAccessor -import org.eclipse.n4js.ts.types.InferenceVariable -import org.eclipse.n4js.ts.types.TGetter -import org.eclipse.n4js.ts.types.TMethod -import org.eclipse.n4js.ts.types.TStructGetter -import org.eclipse.n4js.ts.types.TStructMember -import org.eclipse.n4js.ts.types.TStructuralType -import org.eclipse.n4js.ts.types.TypingStrategy -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.utils.RuleEnvironment -import org.eclipse.n4js.typesystem.utils.TypeSystemHelper -import org.eclipse.n4js.utils.EcoreUtilN4 -import org.eclipse.n4js.utils.N4JSLanguageUtils - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * {@link PolyProcessor} delegates here for processing array literals. - * - * @see PolyProcessor#inferType(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,ASTMetaInfoCache) - * @see PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache) - */ -@Singleton -package class PolyProcessor_ObjectLiteral extends AbstractPolyProcessor { - - @Inject - private PolyProcessor polyProcessor; - - @Inject - private N4JSTypeSystem ts; - @Inject - private TypeSystemHelper tsh; - - /** - * BEFORE CHANGING THIS METHOD, READ THIS: - * {@link PolyProcessor#processExpr(RuleEnvironment,org.eclipse.n4js.n4JS.Expression,TypeRef,InferenceContext,ASTMetaInfoCache)} - */ - def package TypeRef processObjectLiteral(RuleEnvironment G, ObjectLiteral objLit, TypeRef expectedTypeRef, - InferenceContext infCtx, ASTMetaInfoCache cache) { - - if (!objLit.isPoly) { - val result = ts.type(G, objLit); - // do not store in cache (TypeProcessor responsible for storing types of non-poly expressions in cache) - return result; - } - - // quick mode as a performance tweak: - val haveUsableExpectedType = expectedTypeRef !== null && expectedTypeRef.structuralTyping; // TODO reconsider - val quickMode = !haveUsableExpectedType && !TypeUtils.isInferenceVariable(expectedTypeRef); - - val List tMembers = newArrayList; - // in standard mode: the following list will contain pairs from property assignments to inference variables - // in quick mode: the following list will contain pairs from property assignments to fallback types - val List> props2InfVarOrFallbackType = newArrayList; - processProperties(G, cache, infCtx, objLit, tMembers, quickMode, props2InfVarOrFallbackType); - linkGetterSetterPairs(infCtx, tMembers, quickMode); - - // create temporary type (i.e. may contain inference variables) - val resultTypeRef = TypeUtils.createParameterizedTypeRefStructural(G.objectType, TypingStrategy.STRUCTURAL, tMembers); - resultTypeRef.ASTNodeOptionalFieldStrategy = OptionalFieldStrategy.FIELDS_AND_ACCESSORS_OPTIONAL; - - // register onSolved handlers to add final types to cache (i.e. may not contain inference variables) - infCtx.onSolved [ solution | handleOnSolved(G, cache, infCtx, objLit, quickMode, props2InfVarOrFallbackType, solution) ]; - - // return temporary type of objLit (i.e. may contain inference variables) - return resultTypeRef; - } - - /** - * Processes all properties of an object literal. - * As of now, methods are not processed. - */ - def private void processProperties(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, - ObjectLiteral objLit, List tMembers, - boolean quickMode, List> props2InfVarOrFallbackType - ) { - for (pa : objLit.propertyAssignments) { - if (pa !== null) { - var TStructMember tMember = null; - if (pa.definedMember !== null) { - tMember = TypeUtils.copy(pa.definedMember); - tMembers += tMember; - } else { - // this happens if member name in AST was null (bad syntax in code; types builder won't - // create a TMember in that case) or in case of an invalid computed name (not a compile-time - // expression, unresolved reference, etc.; ComputedNameProcessor will remote TMember for - // consistency with first case); this is an error case, but we still have to continue - // processing, because nested expression must still be typed as usual. - } - - if (pa.isPoly) { - if (!(tMember instanceof TMethod)) { - processNonMethodProperties(G, cache, infCtx, tMember, pa, - quickMode, props2InfVarOrFallbackType); - } - } - } - } - } - - /** - * For each property in the object literal: - * a) introduce a new inference variable representing the property's type (except for methods) - * b) add a constraint: expressionTypeRef <: iv - * c) create a TStructMember to be used in the structural result type reference - * - * @param tMember may be null in case of invalid members. - */ - def private void processNonMethodProperties(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, - TStructMember tMember, PropertyAssignment propAssignm, - boolean quickMode, List> props2InfVarOrFallbackType - ) { - if(tMember!==null) { - val originalMemberType = tMember.typeOfMember; - - assertTrueIfRigid(cache, "type of " + propAssignm.eClass.name + " in TModule should be a DeferredTypeRef", - originalMemberType instanceof DeferredTypeRef); - - if (!(originalMemberType instanceof DeferredTypeRef)) { - return; // if rigid assertions are turned off, we fail safe (should never happen) - } - } - - if (!quickMode) { - // standard mode: - // create new inference variable for the to-be-inferred type of this property - val iv = infCtx.newInferenceVariable; - // set it as type in tMember - if (tMember !== null) { - tMember.typeOfMember = TypeUtils.createTypeRef(iv); - } - // add a constraint for the initializer expression (if any) - if (propAssignm instanceof PropertyNameValuePair) { - if (propAssignm.expression !== null) { - val exprTypeRef = polyProcessor.processExpr(G, propAssignm.expression, TypeUtils.createTypeRef(iv), infCtx, cache); - infCtx.addConstraint(exprTypeRef, TypeUtils.createTypeRef(iv), Variance.CO); // exprTypeRef <: iv - } - } - // remember for later - props2InfVarOrFallbackType += propAssignm -> iv; - } else { - // quick mode: - // compute a fall-back type - val TypeRef fallbackType = switch (propAssignm) { - PropertyNameValuePair case propAssignm.expression !== null: - polyProcessor.processExpr(G, propAssignm.expression, null, infCtx, cache) - PropertyGetterDeclaration: - propAssignm.declaredTypeOfOtherAccessorInPair ?: G.anyTypeRef - PropertySetterDeclaration: - propAssignm.declaredTypeOfOtherAccessorInPair ?: G.anyTypeRef - default: - G.anyTypeRef - }; - // set it as type in tMember - if (tMember !== null) { - tMember.typeOfMember = TypeUtils.copy(fallbackType); - } - // remember for later - props2InfVarOrFallbackType += propAssignm -> fallbackType; - } - } - - - def private TypeRef getDeclaredTypeOfOtherAccessorInPair(org.eclipse.n4js.n4JS.FieldAccessor accAST) { - val otherPair = findOtherAccessorInPair(accAST.definedAccessor); - val otherPairAST = otherPair?.astElement as org.eclipse.n4js.n4JS.FieldAccessor; - val declTypeRef = otherPairAST?.declaredTypeRef; - return declTypeRef; - } - - def private FieldAccessor findOtherAccessorInPair(FieldAccessor acc) { - if (acc?.name !== null) { - val type = acc.eContainer; - if (type instanceof ContainerType) { - val lookForWriteAccess = acc instanceof TGetter; - val result = type.findOwnedMember(acc.name, lookForWriteAccess, acc.static); - if (result instanceof FieldAccessor) { - return result; - } - } - } - return null; - } - - /** - * Add a constraint for each getter/setter pair reflecting the relation between the getter's and setter's type. - * Required to make the getter obtain its implicit type from the corresponding setter, and vice versa. - */ - private def void linkGetterSetterPairs(InferenceContext infCtx, List tMembers, boolean quickMode) { - if(!quickMode) { // not in quick mode - for (tMember : tMembers) { - if (tMember instanceof TStructGetter) { - val tOtherInPair = findOtherAccessorInPair(tMember); - if (tOtherInPair !== null) { - val typeGetter = tMember.typeOfMember; - val typeSetter = tOtherInPair.typeOfMember; - if (TypeUtils.isInferenceVariable(typeGetter) || TypeUtils.isInferenceVariable(typeSetter)) { - infCtx.addConstraint(typeGetter, typeSetter, Variance.CO); - } else { - // do not add a constraint if both types were explicitly declared - // (then this constraint does not apply!!) - } - } - } - } - } - } - - /** - * Writes final types to cache - */ - private def void handleOnSolved(RuleEnvironment G, ASTMetaInfoCache cache, InferenceContext infCtx, ObjectLiteral objLit, - boolean quickMode, List> props2InfVarOrFallbackType, - Optional> solution - ) { - for (propPair : props2InfVarOrFallbackType) { - val propAssignm = propPair.key; - val memberInTModule = propAssignm.definedMember; - if (memberInTModule !== null) { - val memberType = getMemberType(G, infCtx, solution, quickMode, propPair); - val resolveLiteralTypes = if (quickMode) { - // quick mode means we do not have a type expectation, so we handle literal types exactly - // as when inferring the implicit type of variables with an initializer expression - !N4JSASTUtils.isImmutable(propAssignm) - } else { - // standard mode means we have a type expectation; if we resolved here, we might break - // constraints (instead, it's the responsibility of the constraint solver to avoid literal - // types as far as possible) - false - }; - val memberTypeSane = tsh.sanitizeTypeOfVariableFieldPropertyParameter(G, memberType, resolveLiteralTypes); - EcoreUtilN4.doWithDeliver(false, [ - memberInTModule.typeOfMember = TypeUtils.copy(memberTypeSane); - ], memberInTModule); - } - } - - val resultFinal = TypeUtils.createParameterizedTypeRefStructural(G.objectType, TypingStrategy.STRUCTURAL, - objLit.definedType as TStructuralType); - resultFinal.ASTNodeOptionalFieldStrategy = OptionalFieldStrategy.FIELDS_AND_ACCESSORS_OPTIONAL; - cache.storeType(objLit, resultFinal); - - for (currAss : objLit.propertyAssignments) { - if (currAss.definedMember !== null) { - if (currAss instanceof PropertyMethodDeclaration) { - cache.storeType(currAss, TypeUtils.createTypeRef(currAss.definedMember)); - } else { - cache.storeType(currAss, TypeUtils.copy(currAss.definedMember.typeOfMember)); - } - } else { - if (currAss instanceof PropertyNameValuePair && (currAss as PropertyNameValuePair).expression !== null) { - val exprType = ts.type(G, (currAss as PropertyNameValuePair).expression); - cache.storeType(currAss, exprType); - } else { - cache.storeType(currAss, anyTypeRefDynamic(G)); - } - } - } - } - - private def TypeRef getMemberType(RuleEnvironment G, InferenceContext infCtx, Optional> solution, - boolean quickMode, Pair prop2InfVarOrFallbackType - ) { - var TypeRef memberType = null; - val propAssignm = prop2InfVarOrFallbackType.key; - if (solution.present) { - if (quickMode) { - // success case (quick mode): - val fallbackType = prop2InfVarOrFallbackType.value as TypeRef; // value is a TypeRef - if (propAssignm instanceof PropertyNameValuePair) { - memberType = getFinalResultTypeOfNestedPolyExpression(propAssignm.expression) - } else { - memberType = TypeUtils.copy(fallbackType).applySolution(G, solution.get) - } - - } else { - // success case (standard mode): - val infVar = prop2InfVarOrFallbackType.value as InferenceVariable; // value is an infVar - val fromSolution = solution.get.get(infVar); - - if (propAssignm instanceof PropertyNameValuePair) { - val fromCache = if (propAssignm.expression instanceof ObjectLiteral) { - getFinalResultTypeOfNestedPolyExpression(propAssignm.expression) - } else { - null - }; - if (fromCache !== null && ts.equaltypeSucceeded(G, fromCache, fromSolution)) { - // tweak for nested ObjectLiterals in initializer expression of PropertyNameValuePairs: - // the solution from the infCtx will be a StructuralTypeRef with 'genStructuralMembers' - // but the result of the nested poly computation (via the cache) will give us a much more - // efficient StructuralTypeRef with 'structuralType' pointing to the TStructuralType in the TModule - memberType = fromCache - } else { - memberType = fromSolution - } - } else { - memberType = fromSolution - } - } - } else { - // failure case (both modes): - if (propAssignm instanceof PropertyNameValuePair) { - memberType = getFinalResultTypeOfNestedPolyExpression(propAssignm.expression) - memberType = adjustMemberTypeToAvoidMisleadingLiteralTypes(G, infCtx, quickMode, prop2InfVarOrFallbackType, memberType); - } else { - memberType = G.anyTypeRef - } - } - - return memberType; - } - - /** - * Replaces literal types by their base type to avoid confusing error messages. - *

- * Without this tweak, the following code - *

-	 * let x: ~Object with { prop1: string, prop2: number } = { prop1: "hello", prop2: "BAD!" };
-	 * 
- * would produce the misleading error message - *
-	 * ~Object with { prop1: "hello"; prop2: "BAD!" } is not a structural subtype of ~Object with { prop1: string; prop2: number }: prop1 failed: "hello" is not equal to string and 1 more problems.
-	 * 
- * With this tweak it changes to: - *
-	 * ~Object with { prop1: string; prop2: "BAD!" } is not a structural subtype of ~Object with { prop1: string; prop2: number }: prop2 failed: "BAD!" is not equal to number.
-	 * 
- */ - private def TypeRef adjustMemberTypeToAvoidMisleadingLiteralTypes(RuleEnvironment G, InferenceContext infCtx, - boolean quickMode, Pair prop2InfVarOrFallbackType, TypeRef memberType - ) { - if (!quickMode) { - if (memberType instanceof LiteralTypeRef) { - val infVar = prop2InfVarOrFallbackType.value as InferenceVariable; // value is an infVar - if (!infCtx.isPromisingPartialSolution(infVar, memberType)) { - val baseType = N4JSLanguageUtils.getLiteralTypeBase(G, memberType); - if (infCtx.isPromisingPartialSolution(infVar, baseType)) { - return baseType; - } - } - } - } - return memberType; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/RuntimeDependencyProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/RuntimeDependencyProcessor.java new file mode 100644 index 0000000000..a4e835cec4 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/RuntimeDependencyProcessor.java @@ -0,0 +1,252 @@ +/** + * Copyright (c) 2020 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 com.google.common.collect.Iterators.singletonIterator; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.head; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.ModuleRef; +import org.eclipse.n4js.n4JS.N4ClassifierDeclaration; +import org.eclipse.n4js.n4JS.N4JSASTUtils; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.n4JS.TypeReferenceNode; +import org.eclipse.n4js.smith.Measurement; +import org.eclipse.n4js.smith.N4JSDataCollectors; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType; +import org.eclipse.n4js.ts.types.RuntimeDependency; +import org.eclipse.n4js.ts.types.TExportableElement; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.ts.types.TNamespace; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.utils.EcoreUtilN4; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.SCCUtils; +import org.eclipse.xtext.EcoreUtil2; + +import com.google.inject.Singleton; + +/** + * Processor for computing direct load-time dependencies and import retention (during AST traversal) as well as + * load-time dependency cycles (during finalization of post-processing). + */ +@Singleton +@SuppressWarnings("all") +public class RuntimeDependencyProcessor { + /** + * Invoked during AST traversal (and thus during main post-processing). + */ + void recordRuntimeReferencesInCache(final EObject node, final ASTMetaInfoCache cache) { + if (node instanceof IdentifierRef) { + IdentifierRef idRef = (IdentifierRef) node; + if (idRef.eContainingFeature() == N4JSPackage.Literals.NAMED_EXPORT_SPECIFIER__EXPORTED_ELEMENT) { + return; // re-exports are not harmful + } + IdentifiableElement target = idRef.getTargetElement(); + if (N4JSLanguageUtils.hasRuntimeRepresentation(target)) { + cache.elementsReferencedAtRuntime.add(target); + // in case of namespace imports, we also want to remember that the namespace was referenced at run time: + IdentifiableElement targetRaw = idRef.getId(); + if (targetRaw != target && targetRaw instanceof ModuleNamespaceVirtualType) { + cache.elementsReferencedAtRuntime.add(targetRaw); + } + } + } else if (node instanceof N4ClassifierDeclaration) { + N4ClassifierDeclaration cd = (N4ClassifierDeclaration) node; + if (N4JSLanguageUtils.hasRuntimeRepresentation(cd)) { + for (TypeReferenceNode targetTypeRef : cd.getSuperClassifierRefs()) { + Type targetDeclType = targetTypeRef == null || targetTypeRef.getTypeRef() == null + ? null + : targetTypeRef.getTypeRef().getDeclaredType(); + + if (N4JSLanguageUtils.hasRuntimeRepresentation(targetDeclType)) { + cache.elementsReferencedAtRuntime.add(targetDeclType); + + // in case of namespace imports, we also want to remember that the namespace was referenced at + // run time: + @SuppressWarnings("null") + Type namespaceLikeType = targetTypeRef.getTypeRefInAST() == null + || targetTypeRef.getTypeRefInAST().getAstNamespaceLikeRefs() == null + || head(targetTypeRef.getTypeRefInAST().getAstNamespaceLikeRefs()) == null + ? null + : head(targetTypeRef.getTypeRefInAST().getAstNamespaceLikeRefs()) + .getDeclaredType(); + + if (namespaceLikeType instanceof ModuleNamespaceVirtualType + || namespaceLikeType instanceof TNamespace) { + cache.elementsReferencedAtRuntime.add(namespaceLikeType); + } + // remember that the target's containing module was referenced from an + // extends/implements clause: + @SuppressWarnings("null") + TModule targetModule = (!targetDeclType.eIsProxy()) + ? EcoreUtil2.getContainerOfType(targetDeclType, TModule.class) + : null; + if (isDifferentModuleInSameProject(targetModule, cache)) { + if (N4JSASTUtils.isTopLevelCode(node)) { + // nested classifiers not supported yet, but let's be future proof + cache.modulesReferencedAtLoadtimeForInheritance.add(targetModule); + } + } + } + } + } + } + } + + /** + * Invoked at the end of AST traversal (and thus, during main post-processing). + *

+ * Also sets the flag {@link ImportSpecifier#getRetainedAtRuntime() retainedAtRuntime}. + */ + void storeDirectRuntimeDependenciesInTModule(Script script, ASTMetaInfoCache cache) { + try (Measurement m = N4JSDataCollectors.dcRuntimeDepsCollect.getMeasurement()) { + doStoreDirectRuntimeDependenciesInTModule(script, cache); + } + } + + private void doStoreDirectRuntimeDependenciesInTModule(Script script, ASTMetaInfoCache cache) { + TModule module = script.getModule(); + + // step 1: set runtime retention flag in import specifiers + List importDecls = toList(filter(script.getScriptElements(), ImportDeclaration.class)); + for (ImportDeclaration importDecl : importDecls) { + for (ImportSpecifier importSpec : importDecl.getImportSpecifiers()) { + if (importSpec != null) { + TExportableElement element; + if (importSpec instanceof NamedImportSpecifier) { + element = ((NamedImportSpecifier) importSpec).getImportedElement(); + } else if (importSpec instanceof NamespaceImportSpecifier) { + element = ((NamespaceImportSpecifier) importSpec).getDefinedType(); + } else { + throw new IllegalStateException( + "unknown subclass of ImportSpecifier: " + importSpec.eClass().getName()); + } + boolean retained = cache.elementsReferencedAtRuntime.contains(element); + EcoreUtilN4.doWithDeliver(false, () -> importSpec.setRetainedAtRuntime(retained), importSpec); + } + } + } + + // step 2: derive direct runtime dependencies from runtime retention of imports + // + // NOTE: order of the dependencies is important, here, because a change in the TModule has + // significant implications (e.g. dependent modules will be rebuilt, see #isAffected() in + // incremental builder); so we want to avoid a random reordering of the same dependencies and + // therefore use a defined order derived from the order of import declarations in the AST. + // + Set modulesReferencedAtRuntime = new LinkedHashSet<>(); + List refsToOtherModules = toList( + filter(filter(script.getScriptElements(), ModuleRef.class), mr -> mr.isReferringToOtherModule())); + for (ModuleRef moduleRef : refsToOtherModules) { + if (moduleRef.isRetainedAtRuntime()) { // note: will also be true for bare imports and for all re-exports + TModule targetModule = moduleRef.getModule(); + if (isDifferentModuleInSameProject(targetModule, cache)) { + modulesReferencedAtRuntime.add(targetModule); + } + } + } + List dependenciesRuntime = new ArrayList<>(modulesReferencedAtRuntime.size()); + for (TModule currModule : modulesReferencedAtRuntime) { + RuntimeDependency currDep = TypesFactory.eINSTANCE.createRuntimeDependency(); + currDep.setTarget(currModule); + currDep.setLoadtimeForInheritance(cache.modulesReferencedAtLoadtimeForInheritance.contains(currModule)); + dependenciesRuntime.add(currDep); + } + + // step 3: store direct runtime dependencies in TModule + if (module != null) { + EcoreUtilN4.doWithDeliver(false, () -> { + module.getDependenciesRuntime().clear(); + module.getDependenciesRuntime().addAll(dependenciesRuntime); + }, module); + } + } + + /** + * Invoked during the finalization of post-processing (and thus, after the main post-processing of all directly or + * indirectly required resources has finished). + */ + void computeAndStoreRuntimeCyclesInTModule(TModule module) { + try (Measurement m = N4JSDataCollectors.dcRuntimeDepsFindCycles.getMeasurement()) { + doComputeAndStoreRuntimeCyclesInTModule(module); + } + } + + private void doComputeAndStoreRuntimeCyclesInTModule(TModule module) { + // NOTE: as above, the order of cyclicModulesRuntime and runtimeCyclicLoadtimeDependents stored in + // the TModule is important, because we want to avoid random reordering of the same set of modules + // to avoid unnecessary changes of the TModule (see above for details) + List cyclicModulesRuntime = getAllRuntimeCyclicModules(module, false); + List cyclicModulesLoadtimeForInheritance = getAllRuntimeCyclicModules(module, true); + Set runtimeCyclicLoadtimeDependents = new LinkedHashSet<>(); + for (TModule cyclicModule : cyclicModulesRuntime) { + if (hasDirectLoadtimeDependencyTo(cyclicModule, module)) { + runtimeCyclicLoadtimeDependents.add(cyclicModule); + } + } + + EcoreUtilN4.doWithDeliver(false, () -> { + module.getCyclicModulesRuntime().clear(); + module.getCyclicModulesLoadtimeForInheritance().clear(); + module.getRuntimeCyclicLoadtimeDependents().clear(); + module.getCyclicModulesRuntime().addAll(cyclicModulesRuntime); + module.getCyclicModulesLoadtimeForInheritance().addAll(cyclicModulesLoadtimeForInheritance); + module.getRuntimeCyclicLoadtimeDependents().addAll(runtimeCyclicLoadtimeDependents); + }, module); + } + + private static List getAllRuntimeCyclicModules(TModule module, boolean onlyLoadtime) { + // Note on performance: at this point we cannot search *all* strongly connected components in the + // graph, which would be more time efficient, because we are operating on a potentially (and usually) + // incomplete runtime dependency graph during the build. The only thing we can rely on is that the + // direct runtime dependencies of modules in our containing strongly connected component are up to + // date. Therefore, we are here only searching the SCC of 'module' (i.e. pass only 'module' as first + // argument to SCCUtils#findSCCs()) and do not make use of a more sophisticated algorithm for finding + // all SCCs such as Johnson's algorithm (cf. Johnson, SIAM Journal on Computing, Vol. 4, No. 1, 1975). + + List> sccs = SCCUtils.findSCCs( + singletonIterator(module), + m -> map(filter(m.getDependenciesRuntime(), dr -> !onlyLoadtime || dr.isLoadtimeForInheritance()), + dr -> dr.getTarget())); + + List cyclicModules = head(filter(sccs, l -> l.remove(module))); + return cyclicModules; + } + + private boolean hasDirectLoadtimeDependencyTo(TModule from, TModule to) { + return exists(from.getDependenciesRuntime(), rd -> rd.isLoadtimeForInheritance() && rd.getTarget() == to); + } + + private static boolean isDifferentModuleInSameProject(TModule module, ASTMetaInfoCache cache) { + return module != null && !module.eIsProxy() + && module.eResource() != cache.getResource() + && java.util.Objects.equals(module.getProjectID(), cache.getProjectID()); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/RuntimeDependencyProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/RuntimeDependencyProcessor.xtend deleted file mode 100644 index 158fdefbfb..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/RuntimeDependencyProcessor.xtend +++ /dev/null @@ -1,218 +0,0 @@ -/** - * Copyright (c) 2020 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.common.collect.Iterators -import com.google.inject.Singleton -import java.util.ArrayList -import java.util.LinkedHashSet -import java.util.List -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.n4JS.IdentifierRef -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.ImportSpecifier -import org.eclipse.n4js.n4JS.ModuleRef -import org.eclipse.n4js.n4JS.N4ClassifierDeclaration -import org.eclipse.n4js.n4JS.N4JSASTUtils -import org.eclipse.n4js.n4JS.N4JSPackage -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.smith.N4JSDataCollectors -import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType -import org.eclipse.n4js.ts.types.RuntimeDependency -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.ts.types.TNamespace -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.utils.EcoreUtilN4 -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.SCCUtils -import org.eclipse.xtext.EcoreUtil2 - -/** - * Processor for computing direct load-time dependencies and import retention (during AST traversal) - * as well as load-time dependency cycles (during finalization of post-processing). - */ -@Singleton -class RuntimeDependencyProcessor { - - /** - * Invoked during AST traversal (and thus during main post-processing). - */ - def package recordRuntimeReferencesInCache(EObject node, ASTMetaInfoCache cache) { - if (node instanceof IdentifierRef) { - if (node.eContainingFeature === N4JSPackage.Literals.NAMED_EXPORT_SPECIFIER__EXPORTED_ELEMENT) { - return; // re-exports are not harmful - } - val target = node.targetElement; - if (N4JSLanguageUtils.hasRuntimeRepresentation(target)) { - cache.elementsReferencedAtRuntime += target; - // in case of namespace imports, we also want to remember that the namespace was referenced at run time: - val targetRaw = node.id; - if (targetRaw !== target && targetRaw instanceof ModuleNamespaceVirtualType) { - cache.elementsReferencedAtRuntime += targetRaw; - } - } - } else if (node instanceof N4ClassifierDeclaration) { - if (N4JSLanguageUtils.hasRuntimeRepresentation(node)) { - val targetTypeRefs = node.superClassifierRefs; - for (targetTypeRef : targetTypeRefs) { - val targetDeclType = targetTypeRef?.typeRef?.declaredType; - if (N4JSLanguageUtils.hasRuntimeRepresentation(targetDeclType)) { - cache.elementsReferencedAtRuntime += targetDeclType; - // in case of namespace imports, we also want to remember that the namespace was referenced at run time: - val namespaceLikeType = targetTypeRef.typeRefInAST?.astNamespaceLikeRefs?.head?.declaredType; - if (namespaceLikeType instanceof ModuleNamespaceVirtualType || namespaceLikeType instanceof TNamespace) { - cache.elementsReferencedAtRuntime += namespaceLikeType; - } - // remember that the target's containing module was referenced from an extends/implements clause: - val targetModule = if (!targetDeclType.eIsProxy) EcoreUtil2.getContainerOfType(targetDeclType, TModule); - if (isDifferentModuleInSameProject(targetModule, cache)) { - if (N4JSASTUtils.isTopLevelCode(node)) { // nested classifiers not supported yet, but let's be future proof - cache.modulesReferencedAtLoadtimeForInheritance += targetModule; - } - } - } - } - } - } - } - - /** - * Invoked at the end of AST traversal (and thus, during main post-processing). - *

- * Also sets the flag {@link ImportSpecifier#getRetainedAtRuntime() retainedAtRuntime}. - */ - def package void storeDirectRuntimeDependenciesInTModule(Script script, ASTMetaInfoCache cache) { - val m = N4JSDataCollectors.dcRuntimeDepsCollect.getMeasurement(); - try { - doStoreDirectRuntimeDependenciesInTModule(script, cache) - } finally { - m.close(); - } - } - - def private void doStoreDirectRuntimeDependenciesInTModule(Script script, ASTMetaInfoCache cache) { - val module = script.module; - - // step 1: set runtime retention flag in import specifiers - val importDecls = script.scriptElements.filter(ImportDeclaration).toList; - for (importDecl : importDecls) { - for (importSpec : importDecl.importSpecifiers) { - if (importSpec !== null) { - val element = switch(importSpec) { - NamedImportSpecifier: importSpec.importedElement - NamespaceImportSpecifier: importSpec.definedType - default: throw new IllegalStateException("unknown subclass of ImportSpecifier: " + importSpec.eClass.name) - }; - val retained = cache.elementsReferencedAtRuntime.contains(element); - EcoreUtilN4.doWithDeliver(false, [ - importSpec.retainedAtRuntime = retained; - ], importSpec); - } - } - } - - // step 2: derive direct runtime dependencies from runtime retention of imports - // - // NOTE: order of the dependencies is important, here, because a change in the TModule has - // significant implications (e.g. dependent modules will be rebuilt, see #isAffected() in - // incremental builder); so we want to avoid a random reordering of the same dependencies and - // therefore use a defined order derived from the order of import declarations in the AST. - // - val modulesReferencedAtRuntime = newLinkedHashSet; - val refsToOtherModules = script.scriptElements.filter(ModuleRef).filter[referringToOtherModule].toList; - for (moduleRef : refsToOtherModules) { - if (moduleRef.retainedAtRuntime) { // note: will also be true for bare imports and for all re-exports - val targetModule = moduleRef.module; - if (isDifferentModuleInSameProject(targetModule, cache)) { - modulesReferencedAtRuntime += targetModule; - } - } - } - val dependenciesRuntime = new ArrayList(modulesReferencedAtRuntime.size); - for (currModule : modulesReferencedAtRuntime) { - val currDep = TypesFactory.eINSTANCE.createRuntimeDependency(); - currDep.target = currModule; - currDep.loadtimeForInheritance = cache.modulesReferencedAtLoadtimeForInheritance.contains(currModule); - dependenciesRuntime += currDep; - } - - // step 3: store direct runtime dependencies in TModule - if (module !== null) { - EcoreUtilN4.doWithDeliver(false, [ - module.dependenciesRuntime.clear(); - module.dependenciesRuntime += dependenciesRuntime; - ], module); - } - } - - /** - * Invoked during the finalization of post-processing (and thus, after the main post-processing of all - * directly or indirectly required resources has finished). - */ - def package void computeAndStoreRuntimeCyclesInTModule(TModule module) { - val m = N4JSDataCollectors.dcRuntimeDepsFindCycles.getMeasurement(); - try { - doComputeAndStoreRuntimeCyclesInTModule(module); - } finally { - m.close(); - } - } - - def private void doComputeAndStoreRuntimeCyclesInTModule(TModule module) { - // NOTE: as above, the order of cyclicModulesRuntime and runtimeCyclicLoadtimeDependents stored in - // the TModule is important, because we want to avoid random reordering of the same set of modules - // to avoid unnecessary changes of the TModule (see above for details) - val cyclicModulesRuntime = getAllRuntimeCyclicModules(module, false); - val cyclicModulesLoadtimeForInheritance = getAllRuntimeCyclicModules(module, true); - val runtimeCyclicLoadtimeDependents = new LinkedHashSet(); - for (cyclicModule : cyclicModulesRuntime) { - if (cyclicModule.hasDirectLoadtimeDependencyTo(module)) { - runtimeCyclicLoadtimeDependents.add(cyclicModule); - } - } - - EcoreUtilN4.doWithDeliver(false, [ - module.cyclicModulesRuntime.clear(); - module.cyclicModulesLoadtimeForInheritance.clear(); - module.runtimeCyclicLoadtimeDependents.clear(); - module.cyclicModulesRuntime += cyclicModulesRuntime; - module.cyclicModulesLoadtimeForInheritance += cyclicModulesLoadtimeForInheritance; - module.runtimeCyclicLoadtimeDependents += runtimeCyclicLoadtimeDependents; - ], module); - } - - def private static List getAllRuntimeCyclicModules(TModule module, boolean onlyLoadtime) { - // Note on performance: at this point we cannot search *all* strongly connected components in the - // graph, which would be more time efficient, because we are operating on a potentially (and usually) - // incomplete runtime dependency graph during the build. The only thing we can rely on is that the - // direct runtime dependencies of modules in our containing strongly connected component are up to - // date. Therefore, we are here only searching the SCC of 'module' (i.e. pass only 'module' as first - // argument to SCCUtils#findSCCs()) and do not make use of a more sophisticated algorithm for finding - // all SCCs such as Johnson's algorithm (cf. Johnson, SIAM Journal on Computing, Vol. 4, No. 1, 1975). - val sccs = SCCUtils.findSCCs(Iterators.singletonIterator(module), [m| - m.dependenciesRuntime.filter[!onlyLoadtime || isLoadtimeForInheritance].map[target] - ]); - val cyclicModules = sccs.filter[remove(module)].head; - return cyclicModules; - } - - def private boolean hasDirectLoadtimeDependencyTo(TModule from, TModule to) { - return from.dependenciesRuntime.exists[isLoadtimeForInheritance && target === to]; - } - - def private static boolean isDifferentModuleInSameProject(TModule module, ASTMetaInfoCache cache) { - return module !== null && !module.eIsProxy - && module.eResource !== cache.resource - && module.projectID == cache.projectID; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeDeferredProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeDeferredProcessor.java new file mode 100644 index 0000000000..0af681cbc4 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeDeferredProcessor.java @@ -0,0 +1,170 @@ +/** + * 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.getContextResource; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.n4JS.FunctionDefinition; +import org.eclipse.n4js.n4JS.FunctionExpression; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4GetterDeclaration; +import org.eclipse.n4js.n4JS.N4JSASTUtils; +import org.eclipse.n4js.n4JS.N4MethodDeclaration; +import org.eclipse.n4js.n4JS.PropertyMethodDeclaration; +import org.eclipse.n4js.n4JS.SetterDeclaration; +import org.eclipse.n4js.n4JS.TypedElement; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.ThisTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeArgument; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory; +import org.eclipse.n4js.ts.types.ContainerType; +import org.eclipse.n4js.ts.types.TField; +import org.eclipse.n4js.ts.types.TFormalParameter; +import org.eclipse.n4js.ts.types.TGetter; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.ts.types.TTypedElement; +import org.eclipse.n4js.ts.types.TVariable; +import org.eclipse.n4js.ts.types.TypableElement; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.typesystem.N4JSTypeSystem; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper; +import org.eclipse.n4js.utils.EcoreUtilN4; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * Processor for handling {@link DeferredTypeRef}s, except those related to poly expressions, which are handled + * by the {@link PolyProcessor}s. + */ +@Singleton +class TypeDeferredProcessor extends AbstractProcessor { + + @Inject + private N4JSTypeSystem ts; + @Inject + private TypeSystemHelper tsh; + + void handleDeferredTypeRefs_preChildren(RuleEnvironment G, EObject astNode, ASTMetaInfoCache cache) { + // DeferredTypeRefs related to poly expressions should not be handled here (poly computer responsible for this!) + if (astNode instanceof N4MethodDeclaration) { + N4MethodDeclaration md = (N4MethodDeclaration) astNode; + TypeRef declReturnTypeRefInAST = md.getDeclaredReturnTypeRefNode() == null ? null + : md.getDeclaredReturnTypeRefNode().getTypeRefInAST(); + if (md.isConstructor()) { + TMethod tCtor = (TMethod) md.getDefinedType(); + if (null != tCtor) { + assertTrueIfRigid(cache, "TMethod in TModule should be a constructor", tCtor.isConstructor()); + assertTrueIfRigid(cache, "return type of constructor in TModule should be a DeferredTypeRef", + tCtor.getReturnTypeRef() instanceof DeferredTypeRef); + TypeRef implicitReturnTypeRef = TypeRefsFactory.eINSTANCE.createThisTypeRefNominal(); + TypeRef boundThisTypeRef = tsh.bindAndSubstituteThisTypeRef(G, md, implicitReturnTypeRef); + EcoreUtilN4.doWithDeliver(false, () -> { + tCtor.setReturnValueMarkedOptional(true); + tCtor.setReturnTypeRef(TypeUtils.copy(boundThisTypeRef)); + }, tCtor); + } + } else if (declReturnTypeRefInAST instanceof ThisTypeRef) { + TMethod tMethod = (TMethod) md.getDefinedType(); + if (null != tMethod) { + assertTrueIfRigid(cache, "return type of TMethod in TModule should be a DeferredTypeRef", + tMethod.getReturnTypeRef() instanceof DeferredTypeRef); + TypeRef boundThisTypeRef = tsh.bindAndSubstituteThisTypeRef(G, declReturnTypeRefInAST, + declReturnTypeRefInAST); + EcoreUtilN4.doWithDeliver(false, () -> tMethod.setReturnTypeRef(TypeUtils.copy(boundThisTypeRef)), + tMethod); + } + } + + } else if (astNode instanceof N4GetterDeclaration) { + N4GetterDeclaration gd = (N4GetterDeclaration) astNode; + TypeRef declReturnTypeRefInAST = gd.getDeclaredTypeRefNode() == null ? null + : gd.getDeclaredTypeRefNode().getTypeRefInAST(); + if (declReturnTypeRefInAST instanceof ThisTypeRef) { + TGetter tGetter = gd.getDefinedGetter(); + assertTrueIfRigid(cache, "return type of TGetter in TModule should be a DeferredTypeRef", + tGetter.getTypeRef() instanceof DeferredTypeRef); + // G |~ methodDecl.returnTypeRef ~> boundThisTypeRef + TypeRef boundThisTypeRef = tsh.getThisTypeAtLocation(G, declReturnTypeRefInAST); + EcoreUtilN4.doWithDeliver(false, () -> tGetter.setTypeRef(TypeUtils.copy(boundThisTypeRef)), tGetter); + } + } + } + + void handleDeferredTypeRefs_postChildren(RuleEnvironment G, EObject astNode, ASTMetaInfoCache cache) { + // DeferredTypeRefs related to poly expressions should not be handled here (poly computer responsible for this!) + if (astNode instanceof VariableDeclaration) { + VariableDeclaration vd = (VariableDeclaration) astNode; + TVariable tVariable = vd.getDefinedVariable(); + setTypeRef(vd, tVariable, false, G, cache); + } else if (astNode instanceof N4FieldDeclaration) { + N4FieldDeclaration fd = (N4FieldDeclaration) astNode; + TField tField = fd.getDefinedField(); + setTypeRef(fd, tField, true, G, cache); + } else if (astNode instanceof FormalParameter) { + FormalParameter fp = (FormalParameter) astNode; + EObject parent = astNode.eContainer(); + if (parent instanceof FunctionExpression) { + // do nothing since its DeferredTypes are computed in PolyProcessor_FunctionExpression + } else if (parent instanceof PropertyMethodDeclaration) { + // do nothing since its DeferredTypes are computed in PolyProcessor_ObjectLiteral + } else if (parent instanceof FunctionDefinition) { + TFormalParameter tFPar = fp.getDefinedVariable(); // tFPar can be null if we have a broken AST + if (tFPar != null && tFPar.getTypeRef() instanceof DeferredTypeRef) { + setTypeRef(fp, tFPar, true, G, cache); + } + } else if (parent instanceof SetterDeclaration) { + // do nothing since setters don't have Deferred Types (and cannot have a default initializer) + } else { + throw new IllegalArgumentException("Unsupported parent type of FormalParameter"); + } + } + } + + private void setTypeRef(T elemInAST, TTypedElement elemInTModule, + boolean useContext, + RuleEnvironment G, ASTMetaInfoCache cache) { + + if (elemInAST.getDeclaredTypeRef() == null) { + if (elemInTModule != null) { // note: tte==null happens if obj.name==null (see types builder) + assertTrueIfRigid(cache, + "return type of " + elemInAST.getClass().getName() + " in TModule should be a DeferredTypeRef", + elemInTModule.getTypeRef() instanceof DeferredTypeRef); + + RuleEnvironment G2 = G; + if (useContext) { + ParameterizedTypeRef context = null; + if (elemInTModule.eContainer() instanceof ContainerType) { + context = TypeUtils.createTypeRef((ContainerType) elemInTModule.eContainer()); + } + G2 = ts.createRuleEnvironmentForContext(context, getContextResource(G)); + } + // delegate to rule caseN4FieldDeclaration, etc. + TypeArgument typeRef = invokeTypeJudgmentToInferType(G2, elemInAST); + if (useContext) { + typeRef = ts.substTypeVariables(G2, typeRef); + } + // this runs after TypeAliasProcessor, so we need to resolve here + TypeArgument typeRefResolved = tsh.resolveTypeAliases(G, typeRef); + TypeRef typeRefSane = tsh.sanitizeTypeOfVariableFieldPropertyParameter(G, typeRefResolved, + !N4JSASTUtils.isImmutable(elemInAST)); + EcoreUtilN4.doWithDeliver(false, () -> elemInTModule.setTypeRef(TypeUtils.copy(typeRefSane)), + elemInTModule); + } + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeDeferredProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeDeferredProcessor.xtend deleted file mode 100644 index aa5dfaf8bc..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeDeferredProcessor.xtend +++ /dev/null @@ -1,164 +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.FormalParameter -import org.eclipse.n4js.n4JS.FunctionDefinition -import org.eclipse.n4js.n4JS.FunctionExpression -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.N4GetterDeclaration -import org.eclipse.n4js.n4JS.N4JSASTUtils -import org.eclipse.n4js.n4JS.N4MethodDeclaration -import org.eclipse.n4js.n4JS.PropertyMethodDeclaration -import org.eclipse.n4js.n4JS.SetterDeclaration -import org.eclipse.n4js.n4JS.TypedElement -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.ThisTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeArgument -import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory -import org.eclipse.n4js.ts.types.ContainerType -import org.eclipse.n4js.ts.types.TMethod -import org.eclipse.n4js.ts.types.TTypedElement -import org.eclipse.n4js.ts.types.TypableElement -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.typesystem.N4JSTypeSystem -import org.eclipse.n4js.typesystem.utils.RuleEnvironment -import org.eclipse.n4js.typesystem.utils.TypeSystemHelper -import org.eclipse.n4js.utils.EcoreUtilN4 - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * Processor for handling {@link DeferredTypeRef}s, except those related to poly expressions, which are handled - * by the {@link PolyProcessor}s. - */ -@Singleton -package class TypeDeferredProcessor extends AbstractProcessor { - - @Inject - private N4JSTypeSystem ts; - @Inject - private TypeSystemHelper tsh; - - def void handleDeferredTypeRefs_preChildren(RuleEnvironment G, EObject astNode, ASTMetaInfoCache cache) { - // DeferredTypeRefs related to poly expressions should not be handled here (poly computer responsible for this!) - switch (astNode) { - N4MethodDeclaration: { - val declReturnTypeRefInAST = astNode.declaredReturnTypeRefNode?.typeRefInAST; - if (astNode.isConstructor) { - val tCtor = astNode.definedType as TMethod; - if (null !== tCtor) { - assertTrueIfRigid(cache, "TMethod in TModule should be a constructor", tCtor.isConstructor); - assertTrueIfRigid(cache, "return type of constructor in TModule should be a DeferredTypeRef", - tCtor.returnTypeRef instanceof DeferredTypeRef); - val implicitReturnTypeRef = TypeRefsFactory.eINSTANCE.createThisTypeRefNominal; - val boundThisTypeRef = tsh.bindAndSubstituteThisTypeRef(G, astNode, implicitReturnTypeRef); - EcoreUtilN4.doWithDeliver(false, [ - tCtor.returnValueMarkedOptional = true; - tCtor.returnTypeRef = TypeUtils.copy(boundThisTypeRef); - ], tCtor); - } - } else if (declReturnTypeRefInAST instanceof ThisTypeRef) { - val tMethod = astNode.definedType as TMethod; - if (null !== tMethod) { - assertTrueIfRigid(cache, "return type of TMethod in TModule should be a DeferredTypeRef", - tMethod.returnTypeRef instanceof DeferredTypeRef); - val boundThisTypeRef = tsh.bindAndSubstituteThisTypeRef(G, declReturnTypeRefInAST, declReturnTypeRefInAST); - EcoreUtilN4.doWithDeliver(false, [ - tMethod.returnTypeRef = TypeUtils.copy(boundThisTypeRef); - ], tMethod); - } - } - } - N4GetterDeclaration: { - val declReturnTypeRefInAST = astNode.declaredTypeRefNode?.typeRefInAST; - if (declReturnTypeRefInAST instanceof ThisTypeRef) { - val tGetter = astNode.definedGetter; - assertTrueIfRigid(cache, "return type of TGetter in TModule should be a DeferredTypeRef", - tGetter.typeRef instanceof DeferredTypeRef); - val boundThisTypeRef = tsh.getThisTypeAtLocation(G, declReturnTypeRefInAST); // G |~ methodDecl.returnTypeRef ~> boundThisTypeRef - EcoreUtilN4.doWithDeliver(false, [ - tGetter.typeRef = TypeUtils.copy(boundThisTypeRef); - ], tGetter); - } - } - }; - } - - def void handleDeferredTypeRefs_postChildren(RuleEnvironment G, EObject astNode, ASTMetaInfoCache cache) { - // DeferredTypeRefs related to poly expressions should not be handled here (poly computer responsible for this!) - switch (astNode) { - VariableDeclaration: { - val tVariable = astNode.definedVariable; - setTypeRef(astNode, tVariable, false, G, cache); - } - N4FieldDeclaration: { - val tField = astNode.definedField; - setTypeRef(astNode, tField, true, G, cache); - } - FormalParameter: { - val parent = astNode.eContainer; - switch (parent) { - FunctionExpression: { - // do nothing since its DeferredTypes are computed in PolyProcessor_FunctionExpression - } - PropertyMethodDeclaration: { - // do nothing since its DeferredTypes are computed in PolyProcessor_ObjectLiteral - } - FunctionDefinition: { - val tFPar = astNode.definedVariable; // tFPar can be null if we have a broken AST - if (tFPar?.typeRef instanceof DeferredTypeRef) { - setTypeRef(astNode, tFPar, true, G, cache); - } - } - SetterDeclaration: { - // do nothing since setters don't have Deferred Types (and cannot have a default initializer) - } - default: - throw new IllegalArgumentException("Unsupported parent type of FormalParameter") - } - } - } - } - - private def void setTypeRef(T elemInAST, TTypedElement elemInTModule, boolean useContext, - RuleEnvironment G, ASTMetaInfoCache cache) { - - if (elemInAST.declaredTypeRef === null) { - if (elemInTModule !== null) { // note: tte===null happens if obj.name===null (see types builder) - assertTrueIfRigid(cache, "return type of "+ elemInAST.class.name +" in TModule should be a DeferredTypeRef", - elemInTModule.typeRef instanceof DeferredTypeRef); - - var RuleEnvironment G2 = G; - if (useContext) { - var ParameterizedTypeRef context = null; - if (elemInTModule.eContainer instanceof ContainerType) - context = TypeUtils.createTypeRef(elemInTModule.eContainer as ContainerType); - G2 = ts.createRuleEnvironmentForContext(context, G.contextResource); - } - var TypeArgument typeRef = invokeTypeJudgmentToInferType(G2, elemInAST); // delegate to rule caseN4FieldDeclaration, etc. - if (useContext) { - typeRef = ts.substTypeVariables(G2, typeRef); - } - val typeRefResolved = tsh.resolveTypeAliases(G, typeRef); // this runs after TypeAliasProcessor, so we need to resolve here - val typeRefSane = tsh.sanitizeTypeOfVariableFieldPropertyParameter(G, typeRefResolved, !N4JSASTUtils.isImmutable(elemInAST)); - EcoreUtilN4.doWithDeliver(false, [ - elemInTModule.typeRef = TypeUtils.copy(typeRefSane); - ], elemInTModule); - } - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeProcessor.java new file mode 100644 index 0000000000..14d165f341 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeProcessor.java @@ -0,0 +1,480 @@ +/** + * 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.anyTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.getCancelIndicator; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.wrapTypeInTypeRef; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.getDefinedTypeModelElement; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.isASTNode; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.isTypableNode; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.isTypeModelElement; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.isEmpty; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.n4js.n4JS.DestructureUtils; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.FieldAccessor; +import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor; +import org.eclipse.n4js.n4JS.N4ClassExpression; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4JSASTUtils; +import org.eclipse.n4js.n4JS.NewExpression; +import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression; +import org.eclipse.n4js.n4JS.PropertyNameValuePair; +import org.eclipse.n4js.n4JS.TypeDefiningElement; +import org.eclipse.n4js.n4JS.TypeReferenceNode; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.YieldExpression; +import org.eclipse.n4js.resource.N4JSResource; +import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef; +import org.eclipse.n4js.ts.typeRefs.OptionalFieldStrategy; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory; +import org.eclipse.n4js.ts.typeRefs.TypeTypeRef; +import org.eclipse.n4js.ts.types.SyntaxRelatedTElement; +import org.eclipse.n4js.ts.types.TypableElement; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.typesystem.N4JSTypeSystem; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.xtext.service.OperationCanceledManager; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * Processor for handling type inference during post-processing of an N4JS resource. Roughly corresponds to 'type' + * judgment in Xsemantics, but handles also more complex cases, e.g. poly expressions. + *

+ * Invoked from {@link ASTProcessor} and delegates to {@link PolyProcessor}s. + */ +@Singleton +public class TypeProcessor extends AbstractProcessor { + + @Inject + private ASTProcessor astProcessor; + @Inject + private PolyProcessor polyProcessor; + @Inject + private DestructureProcessor destructureProcessor; + @Inject + private N4JSTypeSystem ts; + @Inject + private TypeSystemHelper tsh; + @Inject + private OperationCanceledManager operationCanceledManager; + + /** + * If the given AST node is typable this method will infer its type and store the result in the given cache. + *

+ * This method mainly checks if the given node is typable. Main processing is done in + * {@link #typeNode2(RuleEnvironment, TypableElement, ASTMetaInfoCache, int) typeNode2()}. + */ + public void typeNode(RuleEnvironment G, EObject node, ASTMetaInfoCache cache, int indentLevel) { + if (isTypableNode(node)) { + TypableElement nodeCasted = (TypableElement) node; // because #isTypableNode() returned true + // we have a typable node + if (DestructureUtils.isArrayOrObjectLiteralUsedAsDestructuringPattern(node) + && polyProcessor.isEntryPoint(nodeCasted)) { + // special case: array or object literal being used as a destructuring pattern + log(indentLevel, "ignored (array or object literal being used as a destructuring pattern)"); + destructureProcessor.typeDestructuringPattern(G, node, cache); + + } else { + // standard case + typeNode2(G, nodeCasted, cache, indentLevel); + } + } else { + // not a typable node + log(indentLevel, "ignored (not a typable node: " + + (node == null || node.eClass() == null ? null : node.eClass().getName()) + ")"); + } + } + + /** + * Infers type of given AST node and stores the result in the given cache. + *

+ * More precisely: + *

    + *
  1. if given node is part of a poly expression: + *
      + *
    1. if given node is the root of a tree of nested poly expressions (including the case that node is a poly + * expression without any nested poly expressions):
      + * --> inference of entire tree of nested poly expressions AND storage of all results in cache is delegated to class + * {@link PolyProcessor}. + *
    2. otherwise:
      + * --> ignore this node ({@code PolyProcessor} will deal with it when processing the parent poly expression) + *
    + *
  2. otherwise (standard case):
    + * --> infer type of node by asking the TypeJudgment and store the result in the given cache. + *
+ */ + private void typeNode2(RuleEnvironment G, TypableElement node, ASTMetaInfoCache cache, int indentLevel) { + try { + if (polyProcessor.isResponsibleFor(node)) { + if (polyProcessor.isEntryPoint(node)) { + log(indentLevel, "asking PolyComputer ..."); + polyProcessor.inferType(G, (Expression) node, cache); + // in this case, the polyComputer will store the type in the cache; + // also, the poly computer is responsible for replacing all DeferredTypeRefs + assertTrueIfRigid(cache, "poly computer did not replace DeferredTypeRef", () -> { + EObject typeModelElem = getDefinedTypeModelElement(node); + return typeModelElem == null + || isEmpty(filter(typeModelElem.eAllContents(), DeferredTypeRef.class)); + }); + } else { + // we have a poly expression, but one that is nested in another poly expression + // -> ignore here, because polyProcessor will deal with it when processing the parent poly + // expression + log(indentLevel, + "deferred (nested in poly expression --> will be inferred during inference of outer poly expression)"); + + cache.nonEntryPolyProcessorNodes.add(node); + + return; // return only required to avoid confusing logging of cache.getFailSafe(node) below + } + } else { + // ordinary typing of typable AST nodes by asking the TypeJudgment + log(indentLevel, "asking Xsemantics ..."); + TypeRef result = invokeTypeJudgmentToInferType(G, node); + + TypeRef resultAdjusted = adjustResultForLocationInAST(G, result, node); + + // in this case, we are responsible for storing the type in the cache + // (Xsemantics does not know of the cache) + checkCanceled(G); + cache.storeType(node, resultAdjusted); + } + } catch (Throwable th) { + operationCanceledManager.propagateIfCancelException(th); + logException("exception while obtaining type from type system: " + th.getMessage(), th); + th.printStackTrace(); + cache.storeType(node, TypeRefsFactory.eINSTANCE.createUnknownTypeRef()); + } + + log(indentLevel, cache.getTypeFailSafe(node)); + } + + private T adjustResultForLocationInAST(RuleEnvironment G, T typeRef, TypableElement astNode) { + var result = typeRef; + result = adjustForIndexSignatures(result, astNode); + result = adjustForLocationDependentSpecialProperties(G, result, astNode); + return result; + } + + /** + * Poor man's support for index signatures (intended only for .d.ts but must also be checked in N4JS, because N4JS + * may contain classifiers that extend a classifier from .d.ts containing an index signature). + *

+ * TODO IDE-3620 remove this method once index signatures are properly supported + */ + @SuppressWarnings("unchecked") + private T adjustForIndexSignatures(T typeRef, TypableElement astNode) { + EObject parent = astNode == null ? null : astNode.eContainer(); + if (parent instanceof ParameterizedPropertyAccessExpression) { + ParameterizedPropertyAccessExpression ppae = (ParameterizedPropertyAccessExpression) parent; + if (astNode == ppae.getTarget() && !typeRef.isDynamic()) { + if (typeRef instanceof ParameterizedTypeRef) { + if (N4JSLanguageUtils.hasIndexSignature(typeRef)) { + ParameterizedTypeRef typeRefCpy = TypeUtils.copy((ParameterizedTypeRef) typeRef); + typeRefCpy.setDynamic(true); + return (T) typeRefCpy; + } + } + } + } + return typeRef; + } + + /** + * Make sure that the value of the two location-dependent special properties typeOfObjectLiteral and + * typeOfNewExpressionOrFinalNominal in {@link ParameterizedTypeRef} correctly reflect the current + * location in the AST, i.e. the the location of the given astNode, no matter where the type reference + * in the given result stems from. + */ + @SuppressWarnings("unchecked") + private T adjustForLocationDependentSpecialProperties(RuleEnvironment G, T result, + TypableElement astNode) { + T typeRef = result; + if (typeRef instanceof ParameterizedTypeRef) { + TypableElement astNodeSkipParen = N4JSASTUtils.skipParenExpressionDownward(astNode); + OptionalFieldStrategy optionalFieldStrategy = N4JSLanguageUtils.calculateOptionalFieldStrategy(ts, G, + astNodeSkipParen, typeRef); + if (typeRef.getASTNodeOptionalFieldStrategy() != optionalFieldStrategy) { + ParameterizedTypeRef typeRefCpy = TypeUtils.copy((ParameterizedTypeRef) typeRef); + // TODO: also remember the cause for opt-field-strat! + typeRefCpy.setASTNodeOptionalFieldStrategy(optionalFieldStrategy); + return (T) typeRefCpy; + } + } + return result; + } + + // --------------------------------------------------------------------------------------------------------------- + + /** + * This is the single, central method for obtaining the type of a typable element (AST node or TModule element). + * It should never be invoked directly by client code! Instead, client code should always call + * {@link N4JSTypeSystem#type(RuleEnvironment,TypableElement) N4JSTypeSystem#type()}. + *

+ * The behavior of this method depends on the state the containing {@link N4JSResource} is in: + *

    + *
  • before post-processing has started:
    + * -> simply initiate post-processing; once it's finished, return type from AST meta-info cache. + *
  • during post-processing: + *
      + *
    • in case of a backward reference:
      + * -> simply return type from AST meta-info cache. + *
    • in case of a forward reference:
      + * -> trigger forward-processing of the identifiable subtree below the given typable element, see + * {@link #getTypeOfForwardReference(RuleEnvironment,TypableElement,ASTMetaInfoCache) #getTypeOfForwardReference()}, + * which delegates to + * {@link ASTProcessor#processSubtree_forwardReference( RuleEnvironment,TypableElement,ASTMetaInfoCache) + * ASTProcessor#processSubtree_forwardReference()}. + *
    + *
  • after post-processing has completed:
    + * -> simply return type from AST meta-info cache. + *
+ * This overview is simplified, check the code for precise rules! + *

+ * Only a single method delegates here (no one else should call this method): + *

    + *
  1. {@link N4JSTypeSystem#type(RuleEnvironment,TypableElement)} + *
+ */ + public TypeRef getType(RuleEnvironment G, TypableElement objRaw) { + + if (objRaw == null) { + // failing safely here; otherwise we would need preemptive null-checks wherever type inference is applied + return TypeRefsFactory.eINSTANCE.createUnknownTypeRef(); + } + + TypableElement obj = objRaw; + if (objRaw.eIsProxy()) { + ResourceSet resSet = RuleEnvironmentExtensions.getContextResource(G).getResourceSet(); + obj = (TypableElement) EcoreUtil.resolve(objRaw, resSet); + } + + Resource res = obj.eResource(); + if (res instanceof N4JSResource) { + N4JSResource n4Res = (N4JSResource) res; + + if (n4Res.isFullyProcessed() && n4Res.getScript().eIsProxy()) { + // special case: this is a resource loaded from the index! + // -> for now, we entirely by-pass ASTMetaInfoCache and just directly wrap the type model element in a + // TypeRef + if (!isTypeModelElement(obj)) { + throw new IllegalStateException("not a type model element: " + obj); + } + return invokeTypeJudgmentToInferType(G, obj); // obj is a type model element, so this will just wrap it + // in a TypeRef (no actual inference) + } + + // make sure post-processing on the containing N4JS resource is initiated + n4Res.performPostProcessing(getCancelIndicator(G)); + + // NOTE: at this point, we know: if *before* the above call to #performPostProcessing() ... + // a) post-processing of 'res' had not been started yet: it will now be *completed* + // b) post-processing of 'res' was in progress: it will now still be *in progress* + // c) post-processing of 'res' was completed: it will now still be *completed* + // See API doc of method PostProcessingAwareResource#performPostProcessing(CancelIndicator). + + // if post-processing is in progress AND 'obj' is a type model element AND it corresponds to an AST node + // --> redirect processing to the AST node, in order to properly handle backward/forward references, esp. + // forward processing of identifiable subtrees + if (n4Res.isPostProcessing() && isTypeModelElement(obj)) { + EObject astNodeToProcess = (obj instanceof SyntaxRelatedTElement) + // NOTE: we've made sure above that we are *NOT* in a Resource loaded from the index! + ? ((SyntaxRelatedTElement) obj).getAstElement() + : null; + boolean isImplicitArgumentsVariable = (astNodeToProcess instanceof FunctionOrFieldAccessor) + ? ((FunctionOrFieldAccessor) astNodeToProcess).getImplicitArgumentsVariable() == obj + : false; + if (!isImplicitArgumentsVariable) { + if (astNodeToProcess instanceof TypableElement) { + // proceed with the corresponding AST node instead of the type model element + obj = (TypableElement) astNodeToProcess; + } + } + } + + // obtain type of 'obj' + return getTypeInN4JSResource(G, n4Res, obj); + + } else { + + // obj not contained in an N4JSResource -> fall back to default behavior + // can happen for: + // - objects that are not contained in a Resource + // - objects that are contained in a Resource but not an N4JSResource + return invokeTypeJudgmentToInferType(G, obj); + } + } + + /** See {@link TypeProcessor#getType(RuleEnvironment, TypableElement)}. */ + private TypeRef getTypeInN4JSResource(RuleEnvironment G, N4JSResource res, TypableElement obj) { + // obtain type of 'obj' depending on whether it's an AST node or type model element AND depending on current + // load state of containing N4JS resource + if (isTypeModelElement(obj)) { + // for type model elements, we by-pass all caching ... + return invokeTypeJudgmentToInferType(G, obj); // obj is a type model element, so this will just wrap it in a + // TypeRef (no actual inference) + } else if (isASTNode(obj) && isTypableNode(obj)) { + // here we read from the cache (if AST node 'obj' was already processed) or forward-process 'obj' + ASTMetaInfoCache cache = res.getASTMetaInfoCacheVerifyContext(); + if (!res.isPostProcessing() && !res.isFullyProcessed()) { + // we have called #performPostProcessing() on the containing resource above, so this is "impossible" + throw new IllegalStateException( + "post-processing neither in progress nor completed after calling #performPostProcessing() in resource: " + + res.getURI()); + } else if (!cache.isPostProcessing() && !cache.isFullyProcessed()) { + // "res.isProcessing() || res.isFullyProcessed()" but not "cache.isProcessing || cache.isFullyProcessed" + // so: the post-processing flags are out of sync between the resource and cache + // (HINT: if you get an exception here, this often indicates an accidental cache clear; use the + // debug code in ASTMetaInfoCacheHelper to track creation/deletion of typing caches to investigate this) + IllegalStateException e = new IllegalStateException( + "post-processing flags out of sync between resource and cache (hint: this is often caused by an accidental cache clear!!)"); + e.printStackTrace(); // make sure we see this on the console (some clients eat up all exceptions!) + throw e; + } else if (cache.isPostProcessing()) { + + // while AST typing is in progress, just read from the cache we are currently filling + TypeRef resultFromCache = cache.getTypeFailSafe(obj); + + if (resultFromCache == null) { + // cache does not contain type for 'obj' (i.e. not processed yet) + // -> we have a forward reference! + log(0, "***** forward reference to: " + obj); + + return getTypeOfForwardReference(G, obj, cache); + } else { + // cache contains a type for 'obj' (i.e. it was already processed) + // -> simply read from cache + return resultFromCache; + } + } else if (cache.isFullyProcessed()) { + return cache.getType(G, obj); // will throw exception in case of cache miss + } + // should not happen + return TypeRefsFactory.eINSTANCE.createUnknownTypeRef(); + } else { + // a non-typable AST node OR some entity in the TModule for which obj.isTypeModelElement returns false + return TypeRefsFactory.eINSTANCE.createUnknownTypeRef(); + } + } + + /** See {@link TypeProcessor#getType(RuleEnvironment, TypableElement)}. */ + private TypeRef getTypeOfForwardReference(RuleEnvironment G, TypableElement node, ASTMetaInfoCache cache) { + assertTrueIfRigid(cache, "argument 'node' must be an AST node", isASTNode(node)); + + // TODO improve handling of destructuring patterns in ASTProcessor/TypeProcessor + // (this is a temporary hack to avoid many illegal forward references within destructuring patterns) + if (destructureProcessor.isForwardReferenceWhileTypingDestructuringPattern(node)) { + return destructureProcessor.handleForwardReferenceWhileTypingDestructuringPattern(G, node, cache); + } + + boolean isLegal = astProcessor.processSubtree_forwardReference(G, node, cache); + if (isLegal) { + boolean isCyclicForwardReference = cache.astNodesCurrentlyBeingTyped.contains(node); + if (isCyclicForwardReference) { + // in case of a legal cyclic forward reference, we cannot obtain the type of 'node' in the usual + // way by fully processing 'node' and its subtree, so we have to "guess" a type + if (node instanceof VariableDeclaration || node instanceof N4FieldDeclaration || + node instanceof PropertyNameValuePair) { + + Expression expr = getExpressionOfVFP(node); + if (expr instanceof N4ClassExpression) { + return invokeTypeJudgmentToInferType(G, expr); + } + if (expr instanceof NewExpression) { + Expression callee = ((NewExpression) expr).getCallee(); + if (callee instanceof N4ClassExpression) { + TypeRef calleeType = invokeTypeJudgmentToInferType(G, callee); + Type calleeTypeStaticType = tsh.getStaticType(G, (TypeTypeRef) calleeType); + return TypeUtils.createTypeRef(calleeTypeStaticType); + } + } + TypeRef declTypeRef = getDeclaredTypeRefOfVFP(node); + return (declTypeRef != null) ? declTypeRef : anyTypeRef(G); + } else if (node instanceof FieldAccessor) { + TypeRef declTypeRef = ((FieldAccessor) node).getDeclaredTypeRef(); + return (declTypeRef != null) ? declTypeRef : anyTypeRef(G); + } else if (node instanceof TypeDefiningElement) { + return wrapTypeInTypeRef(G, ((TypeDefiningElement) node).getDefinedType()); + } else if (node instanceof Expression && node.eContainer() instanceof YieldExpression) { + return invokeTypeJudgmentToInferType(G, node); + } else { + String msg = "handling of a legal case of cyclic forward references missing in TypeProcessor"; + logErr(msg); + IllegalStateException e = new IllegalStateException(msg); + e.printStackTrace(); + return TypeRefsFactory.eINSTANCE.createUnknownTypeRef(); + } + } else if (astProcessor.isSemiCyclicForwardReferenceInForLoop(node, cache)) { + // semi-cyclic forward reference to a variable declaration in a for in/of loop: + // -> similar to cyclic variable declarations, we have to "guess" a type. + TypeReferenceNode declTypeRefNode = ((VariableDeclaration) node).getDeclaredTypeRefNode(); + TypeRef declTypeRefInAST = declTypeRefNode == null ? null : declTypeRefNode.getTypeRefInAST(); + if (declTypeRefInAST != null && declTypeRefNode != null) { + if (declTypeRefNode.getCachedProcessedTypeRef() != null) { + return declTypeRefNode.getCachedProcessedTypeRef(); + } else { + return tsh.resolveTypeAliases(G, declTypeRefInAST); + } + } + + return anyTypeRef(G); + } else { + // in case of a legal, *non*-cyclic forward reference, we can assume that the subtree below 'node' + // has now been processed, which means node's type is now in the typing cache + return cache.getType(G, node); + } + } else { + String msg = "*#*#*#*#*#* ILLEGAL FORWARD REFERENCE to " + node + + " in " + (node.eResource() == null ? null : node.eResource().getURI()); + logErr(msg); + return TypeRefsFactory.eINSTANCE.createUnknownTypeRef(); + } + } + + // --------------------------------------------------------------------------------------------------------------- + + private static Expression getExpressionOfVFP(EObject vfp) { + if (vfp instanceof VariableDeclaration) { + return ((VariableDeclaration) vfp).getExpression(); + } else if (vfp instanceof N4FieldDeclaration) { + return ((N4FieldDeclaration) vfp).getExpression(); + } else if (vfp instanceof PropertyNameValuePair) { + return ((PropertyNameValuePair) vfp).getExpression(); + } + return null; + } + + private static TypeRef getDeclaredTypeRefOfVFP(EObject vfp) { + if (vfp instanceof VariableDeclaration) { + return ((VariableDeclaration) vfp).getDeclaredTypeRef(); + } else if (vfp instanceof N4FieldDeclaration) { + return ((N4FieldDeclaration) vfp).getDeclaredTypeRef(); + } else if (vfp instanceof PropertyNameValuePair) { + return ((PropertyNameValuePair) vfp).getDeclaredTypeRef(); + } + return null; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeProcessor.xtend deleted file mode 100644 index f5a5de20b2..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeProcessor.xtend +++ /dev/null @@ -1,456 +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.emf.ecore.util.EcoreUtil -import org.eclipse.n4js.n4JS.DestructureUtils -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.FieldAccessor -import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor -import org.eclipse.n4js.n4JS.N4ClassExpression -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.N4JSASTUtils -import org.eclipse.n4js.n4JS.NewExpression -import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression -import org.eclipse.n4js.n4JS.PropertyNameValuePair -import org.eclipse.n4js.n4JS.TypeDefiningElement -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.n4JS.YieldExpression -import org.eclipse.n4js.resource.N4JSResource -import org.eclipse.n4js.ts.typeRefs.DeferredTypeRef -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory -import org.eclipse.n4js.ts.typeRefs.TypeTypeRef -import org.eclipse.n4js.ts.types.SyntaxRelatedTElement -import org.eclipse.n4js.ts.types.TypableElement -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.typesystem.N4JSTypeSystem -import org.eclipse.n4js.typesystem.utils.RuleEnvironment -import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions -import org.eclipse.n4js.typesystem.utils.TypeSystemHelper -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.xtext.service.OperationCanceledManager - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* -import static extension org.eclipse.n4js.utils.N4JSLanguageUtils.* - -/** - * Processor for handling type inference during post-processing of an N4JS resource. Roughly corresponds to - * 'type' judgment in Xsemantics, but handles also more complex cases, e.g. poly expressions. - *

- * Invoked from {@link ASTProcessor} and delegates to {@link PolyProcessor}s. - */ -@Singleton -public class TypeProcessor extends AbstractProcessor { - - @Inject - private ASTProcessor astProcessor; - @Inject - private PolyProcessor polyProcessor; - @Inject - private DestructureProcessor destructureProcessor; - @Inject - private N4JSTypeSystem ts; - @Inject - private TypeSystemHelper tsh; - @Inject - private OperationCanceledManager operationCanceledManager; - - - /** - * If the given AST node is typable this method will infer its type and store the result in the given cache. - *

- * This method mainly checks if the given node is typable. Main processing is done in - * {@link #typeNode2(RuleEnvironment, TypableElement, ASTMetaInfoCache, int) typeNode2()}. - */ - def public void typeNode(RuleEnvironment G, EObject node, ASTMetaInfoCache cache, int indentLevel) { - if (node.isTypableNode) { - val nodeCasted = node as TypableElement; // because #isTypableNode() returned true - // we have a typable node - if (DestructureUtils.isArrayOrObjectLiteralUsedAsDestructuringPattern(node) - && polyProcessor.isEntryPoint(nodeCasted)) { - // special case: array or object literal being used as a destructuring pattern - log(indentLevel, "ignored (array or object literal being used as a destructuring pattern)") - destructureProcessor.typeDestructuringPattern(G, node, cache, indentLevel); - - } else { - // standard case - typeNode2(G, nodeCasted, cache, indentLevel); - } - } else { - // not a typable node - log(indentLevel, "ignored (not a typable node: " + node?.eClass?.name + ")") - } - } - - /** - * Infers type of given AST node and stores the result in the given cache. - *

- * More precisely: - *

    - *
  1. if given node is part of a poly expression: - *
      - *
    1. if given node is the root of a tree of nested poly expressions (including the case that node is a poly - * expression without any nested poly expressions):
      - * --> inference of entire tree of nested poly expressions AND storage of all results in cache is delegated - * to class {@link PolyProcessor}. - *
    2. otherwise:
      - * --> ignore this node ({@code PolyProcessor} will deal with it when processing the parent poly expression) - *
    - *
  2. otherwise (standard case):
    - * --> infer type of node by asking the TypeJudgment and store the result in the given cache. - *
- */ - def private void typeNode2(RuleEnvironment G, TypableElement node, ASTMetaInfoCache cache, int indentLevel) { - try { - if (polyProcessor.isResponsibleFor(node)) { - if (polyProcessor.isEntryPoint(node)) { - log(indentLevel, "asking PolyComputer ..."); - polyProcessor.inferType(G, node as Expression, cache); - // in this case, the polyComputer will store the type in the cache; - // also, the poly computer is responsible for replacing all DeferredTypeRefs - assertTrueIfRigid(cache, "poly computer did not replace DeferredTypeRef", [ - val typeModelElem = node.definedTypeModelElement; - return typeModelElem === null || typeModelElem.eAllContents.filter(DeferredTypeRef).empty - ]); - } else { - // we have a poly expression, but one that is nested in another poly expression - // -> ignore here, because polyProcessor will deal with it when processing the parent poly expression - log(indentLevel, - "deferred (nested in poly expression --> will be inferred during inference of outer poly expression)"); - - cache.nonEntryPolyProcessorNodes.add(node); - - return; // return only required to avoid confusing logging of cache.getFailSafe(node) below - } - } else { - // ordinary typing of typable AST nodes by asking the TypeJudgment - log(indentLevel, "asking Xsemantics ..."); - val result = invokeTypeJudgmentToInferType(G, node); - - val resultAdjusted = adjustResultForLocationInAST(G, result, node); - - // in this case, we are responsible for storing the type in the cache - // (Xsemantics does not know of the cache) - checkCanceled(G); - cache.storeType(node, resultAdjusted); - } - } catch (Throwable th) { - operationCanceledManager.propagateIfCancelException(th); - logException("exception while obtaining type from type system: " + th.message, th); - th.printStackTrace - cache.storeType(node, TypeRefsFactory.eINSTANCE.createUnknownTypeRef); - } - - log(indentLevel, cache.getTypeFailSafe(node)); - } - - def private T adjustResultForLocationInAST(RuleEnvironment G, T typeRef, TypableElement astNode) { - var result = typeRef; - result = adjustForIndexSignatures(G, result, astNode); - result = adjustForLocationDependentSpecialProperties(G, result, astNode); - return result; - } - - /** - * Poor man's support for index signatures (intended only for .d.ts but must also be checked in N4JS, - * because N4JS may contain classifiers that extend a classifier from .d.ts containing an index signature). - *

- * TODO IDE-3620 remove this method once index signatures are properly supported - */ - def private T adjustForIndexSignatures(RuleEnvironment G, T typeRef, TypableElement astNode) { - val parent = astNode?.eContainer; - if (parent instanceof ParameterizedPropertyAccessExpression) { - if (astNode === parent.target && !typeRef.dynamic) { - if (typeRef instanceof ParameterizedTypeRef) { - if (N4JSLanguageUtils.hasIndexSignature(typeRef)) { - val typeRefCpy = TypeUtils.copy(typeRef); - typeRefCpy.dynamic = true; - return typeRefCpy; - } - } - } - } - return typeRef; - } - - /** - * Make sure that the value of the two location-dependent special properties typeOfObjectLiteral and - * typeOfNewExpressionOrFinalNominal in {@link ParameterizedTypeRef} correctly reflect the current - * location in the AST, i.e. the the location of the given astNode, no matter where the type reference - * in the given result stems from. - *

- * For more details see {@link TypeRef#isTypeOfObjectLiteral()}. - */ - def private T adjustForLocationDependentSpecialProperties(RuleEnvironment G, T result, TypableElement astNode) { - val typeRef = result; - if (typeRef instanceof ParameterizedTypeRef) { - val astNodeSkipParen = N4JSASTUtils.skipParenExpressionDownward(astNode); - val optionalFieldStrategy = N4JSLanguageUtils.calculateOptionalFieldStrategy(ts, G, astNodeSkipParen, typeRef); - if (typeRef.ASTNodeOptionalFieldStrategy !== optionalFieldStrategy) { - val typeRefCpy = TypeUtils.copy(typeRef); - typeRefCpy.ASTNodeOptionalFieldStrategy = optionalFieldStrategy; // TODO: also remember the cause for opt-field-strat! - return typeRefCpy; - } - } - return result; - } - - - // --------------------------------------------------------------------------------------------------------------- - - - /** - * This is the single, central method for obtaining the type of a typable element (AST node or TModule element). - * It should never be invoked directly by client code! Instead, client code should always call - * {@link N4JSTypeSystem#type(RuleEnvironment,TypableElement) N4JSTypeSystem#type()}. - *

- * The behavior of this method depends on the state the containing {@link N4JSResource} is in: - *

    - *
  • before post-processing has started:
    - * -> simply initiate post-processing; once it's finished, return type from AST meta-info cache. - *
  • during post-processing: - *
      - *
    • in case of a backward reference:
      - * -> simply return type from AST meta-info cache. - *
    • in case of a forward reference:
      - * -> trigger forward-processing of the identifiable subtree below the given typable element, see - * {@link #getTypeOfForwardReference(RuleEnvironment,TypableElement,ASTMetaInfoCache) #getTypeOfForwardReference()}, - * which delegates to {@link ASTProcessor#processSubtree_forwardReference( - * RuleEnvironment,TypableElement,ASTMetaInfoCache) ASTProcessor#processSubtree_forwardReference()}. - *
    - *
  • after post-processing has completed:
    - * -> simply return type from AST meta-info cache. - *
- * This overview is simplified, check the code for precise rules! - *

- * Only a single method delegates here (no one else should call this method): - *

    - *
  1. {@link N4JSTypeSystem#type(RuleEnvironment,TypableElement)} - *
- */ - def public TypeRef getType(RuleEnvironment G, TypableElement objRaw) { - - if (objRaw === null) { - // failing safely here; otherwise we would need preemptive null-checks wherever type inference is applied - return TypeRefsFactory.eINSTANCE.createUnknownTypeRef; - } - - var obj = if (objRaw.eIsProxy) { - val resSet = RuleEnvironmentExtensions.getContextResource(G).resourceSet; - EcoreUtil.resolve(objRaw, resSet) as TypableElement - } else { - objRaw - }; - - val res = obj.eResource; - if (res instanceof N4JSResource) { - - if (res.isFullyProcessed && res.script.eIsProxy) { - // special case: this is a resource loaded from the index! - // -> for now, we entirely by-pass ASTMetaInfoCache and just directly wrap the type model element in a TypeRef - if (!obj.isTypeModelElement) { - throw new IllegalStateException("not a type model element: " + obj) - } - return invokeTypeJudgmentToInferType(G, obj); // obj is a type model element, so this will just wrap it in a TypeRef (no actual inference) - } - - // make sure post-processing on the containing N4JS resource is initiated - res.performPostProcessing(G.cancelIndicator); - - // NOTE: at this point, we know: if *before* the above call to #performPostProcessing() ... - // a) post-processing of 'res' had not been started yet: it will now be *completed* - // b) post-processing of 'res' was in progress: it will now still be *in progress* - // c) post-processing of 'res' was completed: it will now still be *completed* - // See API doc of method PostProcessingAwareResource#performPostProcessing(CancelIndicator). - - // if post-processing is in progress AND 'obj' is a type model element AND it corresponds to an AST node - // --> redirect processing to the AST node, in order to properly handle backward/forward references, esp. - // forward processing of identifiable subtrees - if(res.isPostProcessing && obj.isTypeModelElement) { - val astNodeToProcess = if (obj instanceof SyntaxRelatedTElement) { - obj.astElement // NOTE: we've made sure above that we are *NOT* in a Resource loaded from the index! - }; - val isImplicitArgumentsVariable = if (astNodeToProcess instanceof FunctionOrFieldAccessor) astNodeToProcess.implicitArgumentsVariable === obj else false; - if (!isImplicitArgumentsVariable) { - if (astNodeToProcess instanceof TypableElement) { - // proceed with the corresponding AST node instead of the type model element - obj = astNodeToProcess; - } - } - } - - // obtain type of 'obj' - return getTypeInN4JSResource(G, res, obj); - - } else { - - // obj not contained in an N4JSResource -> fall back to default behavior - // can happen for: - // - objects that are not contained in a Resource - // - objects that are contained in a Resource but not an N4JSResource - return invokeTypeJudgmentToInferType(G, obj); - } - } - - /** See {@link TypeProcessor#getType(RuleEnvironment,RuleApplicationTrace,TypableElement)}. */ - def private TypeRef getTypeInN4JSResource(RuleEnvironment G, N4JSResource res, TypableElement obj) { - // obtain type of 'obj' depending on whether it's an AST node or type model element AND depending on current - // load state of containing N4JS resource - if (obj.isTypeModelElement) { - // for type model elements, we by-pass all caching ... - return invokeTypeJudgmentToInferType(G, obj); // obj is a type model element, so this will just wrap it in a TypeRef (no actual inference) - } else if (obj.isASTNode && obj.isTypableNode) { - // here we read from the cache (if AST node 'obj' was already processed) or forward-process 'obj' - val cache = res.getASTMetaInfoCacheVerifyContext(); - if (!res.isPostProcessing && !res.isFullyProcessed) { - // we have called #performPostProcessing() on the containing resource above, so this is "impossible" - throw new IllegalStateException("post-processing neither in progress nor completed after calling #performPostProcessing() in resource: " + res.URI); - } else if (!cache.isPostProcessing && !cache.isFullyProcessed) { - // "res.isProcessing() || res.isFullyProcessed()" but not "cache.isProcessing || cache.isFullyProcessed" - // so: the post-processing flags are out of sync between the resource and cache - // (HINT: if you get an exception here, this often indicates an accidental cache clear; use the - // debug code in ASTMetaInfoCacheHelper to track creation/deletion of typing caches to investigate this) - val e = new IllegalStateException("post-processing flags out of sync between resource and cache (hint: this is often caused by an accidental cache clear!!)"); - e.printStackTrace // make sure we see this on the console (some clients eat up all exceptions!) - throw e; - } else if (cache.isPostProcessing) { - - // while AST typing is in progress, just read from the cache we are currently filling - val resultFromCache = cache.getTypeFailSafe(obj); - - if (resultFromCache === null) { - // cache does not contain type for 'obj' (i.e. not processed yet) - // -> we have a forward reference! - log(0, "***** forward reference to: " + obj); - - return getTypeOfForwardReference(G, obj, cache); - } else { - // cache contains a type for 'obj' (i.e. it was already processed) - // -> simply read from cache - return resultFromCache; - } - } else if (cache.isFullyProcessed) { - return cache.getType(G, obj); // will throw exception in case of cache miss - } - } else { - // a non-typable AST node OR some entity in the TModule for which obj.isTypeModelElement returns false - return TypeRefsFactory.eINSTANCE.createUnknownTypeRef; - } - } - - /** See {@link TypeProcessor#getType(RuleEnvironment,RuleApplicationTrace,TypableElement)}. */ - def private TypeRef getTypeOfForwardReference(RuleEnvironment G, TypableElement node, ASTMetaInfoCache cache) { - assertTrueIfRigid(cache, "argument 'node' must be an AST node", node.isASTNode); - - // TODO improve handling of destructuring patterns in ASTProcessor/TypeProcessor - // (this is a temporary hack to avoid many illegal forward references within destructuring patterns) - if (destructureProcessor.isForwardReferenceWhileTypingDestructuringPattern(node)) { - return destructureProcessor.handleForwardReferenceWhileTypingDestructuringPattern(G, node, cache); - } - - val isLegal = astProcessor.processSubtree_forwardReference(G, node, cache); - if (isLegal) { - val isCyclicForwardReference = cache.astNodesCurrentlyBeingTyped.contains(node); - if (isCyclicForwardReference) { - // in case of a legal cyclic forward reference, we cannot obtain the type of 'node' in the usual - // way by fully processing 'node' and its subtree, so we have to "guess" a type - if (node instanceof VariableDeclaration || node instanceof N4FieldDeclaration || - node instanceof PropertyNameValuePair) { - - val expr = node.expressionOfVFP; - if (expr instanceof N4ClassExpression) { - return invokeTypeJudgmentToInferType(G, expr); - } - if (expr instanceof NewExpression) { - val callee = expr.callee; - if (callee instanceof N4ClassExpression) { - val calleeType = invokeTypeJudgmentToInferType(G, callee); - val calleeTypeStaticType = tsh.getStaticType(G, calleeType as TypeTypeRef); - return TypeUtils.createTypeRef(calleeTypeStaticType); - } - } - val declTypeRef = node.declaredTypeRefOfVFP; - return if (declTypeRef !== null) { - declTypeRef - } else { - G.anyTypeRef - }; - } else if (node instanceof FieldAccessor) { - val declTypeRef = node.declaredTypeRef; - return if (declTypeRef !== null) { - declTypeRef - } else { - G.anyTypeRef - }; - } else if (node instanceof TypeDefiningElement) { - return wrapTypeInTypeRef(G, node.definedType); - } else if (node instanceof Expression && node.eContainer instanceof YieldExpression) { - return invokeTypeJudgmentToInferType(G, node); - } else { - val msg = "handling of a legal case of cyclic forward references missing in TypeProcessor"; - logErr(msg); - val e = new IllegalStateException(msg); - e.printStackTrace; - return TypeRefsFactory.eINSTANCE.createUnknownTypeRef; - } - } else if (astProcessor.isSemiCyclicForwardReferenceInForLoop(node, cache)) { - // semi-cyclic forward reference to a variable declaration in a for in/of loop: - // -> similar to cyclic variable declarations, we have to "guess" a type. - val declTypeRefNode = (node as VariableDeclaration).declaredTypeRefNode; - val declTypeRefInAST = declTypeRefNode?.typeRefInAST; - return if (declTypeRefInAST !== null) { - declTypeRefNode.cachedProcessedTypeRef ?: tsh.resolveTypeAliases(G, declTypeRefInAST) - } else { - G.anyTypeRef - }; - } else { - // in case of a legal, *non*-cyclic forward reference, we can assume that the subtree below 'node' - // has now been processed, which means node's type is now in the typing cache - return cache.getType(G, node); - } - } else { - val msg = "*#*#*#*#*#* ILLEGAL FORWARD REFERENCE to " + node + " in " + node.eResource?.URI; - logErr(msg); - return TypeRefsFactory.eINSTANCE.createUnknownTypeRef; - } - } - - - // --------------------------------------------------------------------------------------------------------------- - - - def private static Expression getExpressionOfVFP(EObject vfp) { - switch (vfp) { - VariableDeclaration: - vfp.expression - N4FieldDeclaration: - vfp.expression - PropertyNameValuePair: - vfp.expression - } - } - - def private static TypeRef getDeclaredTypeRefOfVFP(EObject vfp) { - switch (vfp) { - VariableDeclaration: - vfp.declaredTypeRef - N4FieldDeclaration: - vfp.declaredTypeRef - PropertyNameValuePair: - vfp.declaredTypeRef - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeRefProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeRefProcessor.java new file mode 100644 index 0000000000..740ac47c49 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeRefProcessor.java @@ -0,0 +1,201 @@ +/** + * Copyright (c) 2021 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.xtext.xbase.lib.IterableExtensions.findFirst; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.last; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.eclipse.emf.common.util.TreeIterator; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.n4js.n4JS.N4JSASTUtils; +import org.eclipse.n4js.n4JS.TypeDefiningElement; +import org.eclipse.n4js.n4JS.TypeReferenceNode; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.ts.typeRefs.EnumLiteralTypeRef; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.StructuralTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeArgument; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory; +import org.eclipse.n4js.ts.typeRefs.Wildcard; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.TEnum; +import org.eclipse.n4js.ts.types.TEnumLiteral; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.ts.types.TypeAlias; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.typesystem.utils.NestedTypeRefsSwitch; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper; +import org.eclipse.n4js.utils.EcoreUtilN4; + +import com.google.inject.Inject; + +/** + * Processor for converting the raw type references provided by the parser into valid type references that can be used + * internally. + *

+ * Most type references created by the parser are already valid and can be used directly; the only exceptions are: + *

    + *
  • references to the literal of an enum must be converted to an {@link EnumLiteralTypeRef}. + *
  • {@link TypeRef#isAliasUnresolved() unresolved} references to type aliases must be converted to + * {@link TypeRef#isAliasResolved() resolved} references to type aliases. + *
+ */ +class TypeRefProcessor extends AbstractProcessor { + + @Inject + private TypeSystemHelper tsh; + + @SuppressWarnings("unused") + void handleTypeRefs(RuleEnvironment G, EObject astNode, ASTMetaInfoCache cache) { + handleTypeRefsInAST(G, astNode); + handleTypeRefsInTModule(G, astNode); + } + + private void handleTypeRefsInAST(RuleEnvironment G, EObject astNode) { + for (TypeReferenceNode typeRefNode : N4JSASTUtils.getContainedTypeReferenceNodes(astNode)) { + TypeRef typeRef = typeRefNode.getTypeRefInAST(); + TypeRef typeRefProcessed = processTypeArg(G, typeRef); + if (typeRefProcessed != null) { + if (typeRefProcessed == typeRef) { + // temporary tweak to ensure correctness of code base: + // if nothing was resolved, we could directly use 'typeRef' as the value of property + // TypeReferenceNode#cachedProcessedTypeRef; however, then the return value of operation + // TypeReferenceNode#getTypeRef() would sometimes be contained in the AST and sometimes not; + // by always creating a copy here, we force the entire code base to be able to cope with the + // fact that the return value of TypeReferenceNode#getTypeRef() might not be contained in the + // AST (similarly as type aliases were used everywhere in the code, for testing) + typeRefProcessed = TypeUtils.copy(typeRef); + } + TypeRef typeRefProcessedFinal = typeRefProcessed; + EcoreUtilN4.doWithDeliver(false, () -> typeRefNode.setCachedProcessedTypeRef(typeRefProcessedFinal), + typeRefNode); + } + } + } + + private void handleTypeRefsInTModule(RuleEnvironment G, EObject astNode) { + IdentifiableElement defType = null; + + if (astNode instanceof TypeDefiningElement) { + defType = ((TypeDefiningElement) astNode).getDefinedType(); + } else if (astNode instanceof StructuralTypeRef) { + defType = ((StructuralTypeRef) astNode).getStructuralType(); + } else if (astNode instanceof FunctionTypeExpression) { + defType = ((FunctionTypeExpression) astNode).getDeclaredType(); + } else if (astNode instanceof VariableDeclaration) { + defType = ((VariableDeclaration) astNode).getDefinedVariable(); + } + + if (defType != null) { + handleTypeRefsInIdentifiableElement(G, defType); + } + } + + private void handleTypeRefsInIdentifiableElement(RuleEnvironment G, IdentifiableElement elem) { + if (elem instanceof TypeAlias) { + return; // do not resolve the 'actualTypeRef' property in type alias itself + } + List allNestedTypeArgs = new ArrayList<>(); // create list up-front to not confuse tree iterator + // when replacing nodes! + TreeIterator iter = elem.eAllContents(); + while (iter.hasNext()) { + EObject obj = iter.next(); + if (obj instanceof TypeArgument) { + allNestedTypeArgs.add((TypeArgument) obj); + iter.prune(); + } + } + for (TypeArgument typeArg : allNestedTypeArgs) { + TypeArgument typeArgProcessed = processTypeArg(G, typeArg); + if (typeArgProcessed != null && typeArgProcessed != typeArg) { + EReference containmentFeature = typeArg.eContainmentFeature(); + boolean isValidType = containmentFeature != null + && containmentFeature.getEReferenceType().isSuperTypeOf(typeArgProcessed.eClass()); + if (isValidType) { + EcoreUtilN4.doWithDeliver(false, () -> EcoreUtil.replace(typeArg, typeArgProcessed), + typeArg.eContainer()); + } + } + } + } + + /** This overload implements the rule that when passing in a {@link TypeRef}, you get a {@code TypeRef} back. */ + private TypeRef processTypeArg(RuleEnvironment G, TypeRef typeRef) { + return (TypeRef) processTypeArg(G, (TypeArgument) typeRef); + } + + /** + * Guarantee: type references are never converted to {@link Wildcard}s, i.e. when passing in a {@link TypeRef}, you + * get a {@code TypeRef} back. + */ + private TypeArgument processTypeArg(RuleEnvironment G, TypeArgument typeArg) { + if (typeArg == null) { + return null; + } + var processed = typeArg; + + processed = processEnumLiteralTypeRefs(G, processed); + processed = processTypeAliases(G, processed); + + return processed; + } + + private TypeArgument processEnumLiteralTypeRefs(RuleEnvironment G, TypeArgument typeArg) { + // note: we also have to handle parameterized type refs that might be nested below some other TypeRef! + return new ResolveParameterizedTypeRefPointingToTEnumLiteralSwitch(G).doSwitch(typeArg); + } + + private TypeArgument processTypeAliases(RuleEnvironment G, TypeArgument typeArg) { + // note: we also have to resolve type aliases that might be nested below a non-alias TypeRef! + return tsh.resolveTypeAliases(G, typeArg); + } + + private static class ResolveParameterizedTypeRefPointingToTEnumLiteralSwitch extends NestedTypeRefsSwitch { + + ResolveParameterizedTypeRefPointingToTEnumLiteralSwitch(RuleEnvironment G) { + super(G); + } + + @Override + protected ResolveParameterizedTypeRefPointingToTEnumLiteralSwitch derive(RuleEnvironment G_NEW) { + return new ResolveParameterizedTypeRefPointingToTEnumLiteralSwitch(G_NEW); + } + + @Override + protected TypeRef caseParameterizedTypeRef_processDeclaredType(ParameterizedTypeRef typeRef) { + Type astQualifier = typeRef.getAstNamespaceLikeRefs() != null + && last(typeRef.getAstNamespaceLikeRefs()) != null + ? last(typeRef.getAstNamespaceLikeRefs()).getDeclaredType() + : null; + + if (astQualifier instanceof TEnum) { + String enumLiteralName = typeRef.getDeclaredTypeAsText(); + TEnumLiteral enumLiteral = findFirst(((TEnum) astQualifier).getLiterals(), + lit -> Objects.equals(lit.getName(), enumLiteralName)); + if (enumLiteral != null) { + EnumLiteralTypeRef litTypeRef = TypeRefsFactory.eINSTANCE.createEnumLiteralTypeRef(); + litTypeRef.setValue(enumLiteral); + return litTypeRef; + } + } + return typeRef; + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeRefProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeRefProcessor.xtend deleted file mode 100644 index b95af04838..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeRefProcessor.xtend +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Copyright (c) 2021 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 org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.util.EcoreUtil -import org.eclipse.n4js.n4JS.N4JSASTUtils -import org.eclipse.n4js.n4JS.TypeDefiningElement -import org.eclipse.n4js.n4JS.TypeReferenceNode -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.ts.typeRefs.EnumLiteralTypeRef -import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.StructuralTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeArgument -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory -import org.eclipse.n4js.ts.typeRefs.Wildcard -import org.eclipse.n4js.ts.types.IdentifiableElement -import org.eclipse.n4js.ts.types.TEnum -import org.eclipse.n4js.ts.types.TypeAlias -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.typesystem.utils.NestedTypeRefsSwitch -import org.eclipse.n4js.typesystem.utils.RuleEnvironment -import org.eclipse.n4js.typesystem.utils.TypeSystemHelper -import org.eclipse.n4js.utils.EcoreUtilN4 - -/** - * Processor for converting the raw type references provided by the parser into valid type references that - * can be used internally. - *

- * Most type references created by the parser are already valid and can be used directly; the only exceptions - * are: - *

    - *
  • references to the literal of an enum must be converted to an {@link EnumLiteralTypeRef}. - *
  • {@link TypeRef#isAliasUnresolved() unresolved} references to type aliases must be converted to - * {@link TypeRef#isAliasResolved() resolved} references to type aliases. - *
- */ -package class TypeRefProcessor extends AbstractProcessor { - - @Inject - private TypeSystemHelper tsh; - - def void handleTypeRefs(RuleEnvironment G, EObject astNode, ASTMetaInfoCache cache) { - handleTypeRefsInAST(G, astNode); - handleTypeRefsInTModule(G, astNode); - } - - def private void handleTypeRefsInAST(RuleEnvironment G, EObject astNode) { - for (TypeReferenceNode typeRefNode : N4JSASTUtils.getContainedTypeReferenceNodes(astNode)) { - val typeRef = typeRefNode.typeRefInAST; - var typeRefProcessed = processTypeArg(G, typeRef); - if (typeRefProcessed !== null) { - if (typeRefProcessed === typeRef) { - // temporary tweak to ensure correctness of code base: - // if nothing was resolved, we could directly use 'typeRef' as the value of property - // TypeReferenceNode#cachedProcessedTypeRef; however, then the return value of operation - // TypeReferenceNode#getTypeRef() would sometimes be contained in the AST and sometimes not; - // by always creating a copy here, we force the entire code base to be able to cope with the - // fact that the return value of TypeReferenceNode#getTypeRef() might not be contained in the - // AST (similarly as type aliases were used everywhere in the code, for testing) - typeRefProcessed = TypeUtils.copy(typeRef); - } - val typeRefProcessedFinal = typeRefProcessed; - EcoreUtilN4.doWithDeliver(false, [ - typeRefNode.cachedProcessedTypeRef = typeRefProcessedFinal; - ], typeRefNode); - } - } - } - - def private void handleTypeRefsInTModule(RuleEnvironment G, EObject astNode) { - val defType = switch(astNode) { - TypeDefiningElement: - astNode.definedType - StructuralTypeRef: - astNode.structuralType - FunctionTypeExpression: - astNode.declaredType - VariableDeclaration: - astNode.definedVariable - }; - if (defType !== null) { - handleTypeRefsInIdentifiableElement(G, defType); - } - } - - def private void handleTypeRefsInIdentifiableElement(RuleEnvironment G, IdentifiableElement elem) { - if (elem instanceof TypeAlias) { - return; // do not resolve the 'actualTypeRef' property in type alias itself - } - val allNestedTypeArgs = newArrayList; // create list up-front to not confuse tree iterator when replacing nodes! - val iter = elem.eAllContents; - while (iter.hasNext) { - val obj = iter.next; - if (obj instanceof TypeArgument) { - allNestedTypeArgs.add(obj); - iter.prune(); - } - } - for (typeArg : allNestedTypeArgs) { - val typeArgProcessed = processTypeArg(G, typeArg); - if (typeArgProcessed !== null && typeArgProcessed !== typeArg) { - val containmentFeature = typeArg.eContainmentFeature; - val isValidType = containmentFeature !== null - && containmentFeature.getEReferenceType().isSuperTypeOf(typeArgProcessed.eClass); - if (isValidType) { - EcoreUtilN4.doWithDeliver(false, [ - EcoreUtil.replace(typeArg, typeArgProcessed); - ], typeArg.eContainer); - } - } - } - } - - /** This overload implements the rule that when passing in a {@link TypeRef}, you get a {@code TypeRef} back. */ - def private TypeRef processTypeArg(RuleEnvironment G, TypeRef typeRef) { - return processTypeArg(G, typeRef as TypeArgument) as TypeRef; - } - - /** - * Guarantee: type references are never converted to {@link Wildcard}s, i.e. when passing in a {@link TypeRef}, - * you get a {@code TypeRef} back. - */ - def private TypeArgument processTypeArg(RuleEnvironment G, TypeArgument typeArg) { - if (typeArg === null) { - return null; - } - var processed = typeArg; - - processed = processEnumLiteralTypeRefs(G, processed); - processed = processTypeAliases(G, processed); - - return processed; - } - - def private TypeArgument processEnumLiteralTypeRefs(RuleEnvironment G, TypeArgument typeArg) { - // note: we also have to handle parameterized type refs that might be nested below some other TypeRef! - return new ResolveParameterizedTypeRefPointingToTEnumLiteralSwitch(G).doSwitch(typeArg); - } - - def private TypeArgument processTypeAliases(RuleEnvironment G, TypeArgument typeArg) { - // note: we also have to resolve type aliases that might be nested below a non-alias TypeRef! - return tsh.resolveTypeAliases(G, typeArg); - } - - - private static class ResolveParameterizedTypeRefPointingToTEnumLiteralSwitch extends NestedTypeRefsSwitch { - - new(RuleEnvironment G) { - super(G); - } - - override protected derive(RuleEnvironment G_NEW) { - return new ResolveParameterizedTypeRefPointingToTEnumLiteralSwitch(G_NEW); - } - - override protected caseParameterizedTypeRef_processDeclaredType(ParameterizedTypeRef typeRef) { - val astQualifier = typeRef.astNamespaceLikeRefs?.last?.declaredType; - if (astQualifier instanceof TEnum) { - val enumLiteralName = typeRef.declaredTypeAsText; - val enumLiteral = astQualifier.literals.findFirst[name == enumLiteralName]; - if (enumLiteral !== null) { - val litTypeRef = TypeRefsFactory.eINSTANCE.createEnumLiteralTypeRef(); - litTypeRef.value = enumLiteral; - return litTypeRef; - } - } - return typeRef; - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDerivedStateComputer.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDerivedStateComputer.java new file mode 100644 index 0000000000..9057422f25 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDerivedStateComputer.java @@ -0,0 +1,71 @@ +/** + * 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.resource; + +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.typesbuilder.N4JSTypesBuilder; +import org.eclipse.xtext.resource.DerivedStateAwareResource; +import org.eclipse.xtext.resource.IDerivedStateComputer; + +import com.google.inject.Inject; + +/** + * Derives the types model from the AST and stores it at the second index of the resource. See {@link N4JSTypesBuilder}. + */ +public class N4JSDerivedStateComputer implements IDerivedStateComputer { + + @Inject + private N4JSUnloader n4jsUnloader; + @Inject + private N4JSTypesBuilder typesBuilder; + + /** + * Creates an {@link TModule} on the second slot of the resource. when the resource contents is not empty. + */ + @Override + public void installDerivedState(DerivedStateAwareResource resource, boolean preLinkingPhase) { + List contents = resource.getContents(); + if (contents.isEmpty()) { + String msg = "cannot install derived state in resource '" + resource.getURI().toString() + "' without AST"; + throw new IllegalStateException(msg); + } else if (contents.size() == 1) { + typesBuilder.createTModuleFromSource(resource, preLinkingPhase); + } else if (contents.size() == 2) { + typesBuilder.relinkTModuleToSource(resource, preLinkingPhase); + } else { + throw new IllegalStateException("resource '" + resource.getURI().toString() + "' with more than two roots"); + } + } + + /** + * Calls {@link N4JSUnloader#unloadRoot(EObject)} for the second slot root. Then all contents of the resource are + * cleared. + */ + @Override + public void discardDerivedState(DerivedStateAwareResource resource) { + List contents = resource.getContents(); + // resource.getContents().get(1-n)clear + if (contents.isEmpty()) { + return; + } + + // other resources may hold references to the derived state thus we + // have to unload (proxify) it explicitly before it is removed from the resource + List tail = contents.subList(1, contents.size()); + for (EObject eo : tail) { + n4jsUnloader.unloadRoot(eo); + } + tail.clear(); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDerivedStateComputer.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDerivedStateComputer.xtend deleted file mode 100644 index 1714a8d0ef..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDerivedStateComputer.xtend +++ /dev/null @@ -1,62 +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.resource; - -import com.google.inject.Inject -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.typesbuilder.N4JSTypesBuilder -import org.eclipse.xtext.resource.DerivedStateAwareResource -import org.eclipse.xtext.resource.IDerivedStateComputer - -/** - * Derives the types model from the AST and stores it at the second index of the resource. - * See {@link N4JSTypesBuilder}. - */ -public class N4JSDerivedStateComputer implements IDerivedStateComputer { - - @Inject extension N4JSUnloader - @Inject private N4JSTypesBuilder typesBuilder; - - /** - * Creates an {@link TModule} on the second slot of the resource. when the resource contents is not empty. - */ - override void installDerivedState(DerivedStateAwareResource resource, boolean preLinkingPhase) { - val contents = resource.contents; - if (contents.nullOrEmpty) { - val msg = "cannot install derived state in resource '"+ resource.URI.toString +"' without AST"; - throw new IllegalStateException(msg) - } else if (contents.size == 1) { - typesBuilder.createTModuleFromSource(resource, preLinkingPhase); - } else if (contents.size == 2) { - typesBuilder.relinkTModuleToSource(resource, preLinkingPhase); - } else { - throw new IllegalStateException("resource '"+ resource.URI.toString +"' with more than two roots"); - } - } - - /** - * Calls {@link N4JSUnloader#unloadRoot(EObject} for the second slot root. - * Then all contents of the resource are cleared. - */ - override void discardDerivedState(DerivedStateAwareResource resource) { - val contents = resource.contents; - // resource.getContents().get(1-n)clear - if (contents.empty) { - return; - } - - // other resources may hold references to the derived state thus we - // have to unload (proxify) it explicitly before it is removed from the resource - val tail = contents.subList(1, contents.size) - tail.forEach[unloadRoot] - tail.clear - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDescriptionUtils.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDescriptionUtils.java new file mode 100644 index 0000000000..6092ddb6ed --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSDescriptionUtils.java @@ -0,0 +1,28 @@ +/** + * 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.resource; + +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.xtext.resource.DescriptionUtils; +import org.eclipse.xtext.resource.IResourceDescription; + +/** + */ +public class N4JSDescriptionUtils extends DescriptionUtils { + + @Override + public Set collectOutgoingReferences(IResourceDescription description) { + return new HashSet<>(); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSLocationInFileProvider.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSLocationInFileProvider.java new file mode 100644 index 0000000000..74acf719bc --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSLocationInFileProvider.java @@ -0,0 +1,223 @@ +/** + * 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.resource; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst; + +import java.util.List; +import java.util.Objects; + +import org.eclipse.emf.common.notify.Adapter; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.n4js.N4JSGlobals; +import org.eclipse.n4js.n4JS.GenericDeclaration; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.PropertyNameOwner; +import org.eclipse.n4js.n4JS.PropertyNameValuePair; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression; +import org.eclipse.n4js.ts.types.SyntaxRelatedTElement; +import org.eclipse.n4js.ts.types.TFormalParameter; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TStructMember; +import org.eclipse.n4js.ts.types.TStructMethod; +import org.eclipse.n4js.ts.types.TStructuralType; +import org.eclipse.n4js.ts.types.TypeVariable; +import org.eclipse.n4js.utils.URIUtils; +import org.eclipse.n4js.xtext.resource.XITextRegionWithLineInformation; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.resource.DefaultLocationInFileProvider; +import org.eclipse.xtext.resource.ILocationInFileProviderExtension; +import org.eclipse.xtext.util.ITextRegion; + +/** + * A location in file provider that is aware of inferred types. The location of the referenced AST element is used + * instead. + */ +public class N4JSLocationInFileProvider extends DefaultLocationInFileProvider { + + @Override + public ITextRegion getFullTextRegion(EObject element) { + EObject srcObj = convertToSource(element); + if (srcObj == null) { + return null; + } + return super.getFullTextRegion(srcObj); + } + + @Override + public ITextRegion getFullTextRegion(EObject owner, EStructuralFeature feature, int indexInList) { + EObject srcObj = convertToSource(owner); + if (srcObj == null) { + return null; + } + return super.getFullTextRegion(srcObj, feature, indexInList); + } + + @Override + public ITextRegion getSignificantTextRegion(EObject element) { + if (element instanceof NamedImportSpecifier) { + NamedImportSpecifier nis = (NamedImportSpecifier) element; + if (!nis.isDefaultImport()) { + if (nis.getAlias() != null) { + return super.getSignificantTextRegion(element, + N4JSPackage.eINSTANCE.getNamedImportSpecifier_Alias(), -1); + } + } + } else if (element instanceof NamespaceImportSpecifier) { + NamespaceImportSpecifier nis = (NamespaceImportSpecifier) element; + if (nis.getAlias() != null) { + return super.getSignificantTextRegion(element, + N4JSPackage.eINSTANCE.getNamespaceImportSpecifier_Alias(), -1); + } + } + EObject srcObj = convertToSource(element); + if (srcObj == null) { + return null; + } + return super.getSignificantTextRegion(srcObj); + } + + @Override + public ITextRegion getSignificantTextRegion(EObject owner, EStructuralFeature feature, int indexInList) { + EObject srcObj = convertToSource(owner); + if (srcObj == null) { + return null; + } + return super.getSignificantTextRegion(srcObj, feature, indexInList); + } + + @Override + public ITextRegion getTextRegion(EObject object, EStructuralFeature feature, int indexInList, + ILocationInFileProviderExtension.RegionDescription query) { + + EObject srcObj = convertToSource(object); + if (srcObj == null) { + return null; + } + return super.getTextRegion(srcObj, feature, indexInList, query); + } + + @Override + public ITextRegion getTextRegion(EObject object, ILocationInFileProviderExtension.RegionDescription query) { + EObject srcObj = convertToSource(object); + if (srcObj == null) { + return null; + } + return super.getTextRegion(srcObj, query); + } + + @Override + protected ITextRegion doGetTextRegion(EObject obj, /* @NonNull */ RegionDescription query) { + if (Objects.equals(N4JSGlobals.DTS_FILE_EXTENSION, + URIUtils.fileExtension(obj.eResource() == null ? null : obj.eResource().getURI()))) { + for (Adapter adapter : obj.eAdapters()) { + if (adapter instanceof XITextRegionWithLineInformation) { + return toZeroBasedRegion((XITextRegionWithLineInformation) adapter); + } + } + } else if (obj instanceof TMember && !((TMember) obj).getConstituentMembers().isEmpty()) { + TMember tMember = (TMember) obj; + TMember fst = tMember.getConstituentMembers().get(0); + return super.doGetTextRegion(fst.getAstElement(), query); + } else if (obj instanceof PropertyNameValuePair && ((PropertyNameValuePair) obj).getProperty() != null) { + return super.getFullTextRegion(obj, N4JSPackage.eINSTANCE.getPropertyNameValuePair_Property(), 0); + } + return super.doGetTextRegion(obj, query); + } + + // TODO use N4JSASTUtils#getCorrespondingASTNode() instead, if possible + /***/ + public EObject convertToSource(EObject element) { + if (element == null || element.eIsProxy()) { + return null; + } + + EObject astElem = (element instanceof SyntaxRelatedTElement) + ? ((SyntaxRelatedTElement) element).getAstElement() + : null; + + if (element instanceof TypeVariable) { + EObject parentAST = convertToSource(element.eContainer()); + if (parentAST instanceof GenericDeclaration || parentAST instanceof TStructMethod + || parentAST instanceof FunctionTypeExpression) { + String typeVarName = ((TypeVariable) element).getName(); + if (typeVarName != null && typeVarName.trim().length() > 0) { + EObject correspondingTypeVarInAST = null; + if (parentAST instanceof GenericDeclaration) { + correspondingTypeVarInAST = findFirst(((GenericDeclaration) parentAST) + .getTypeVars(), tv -> Objects.equals(tv.getName(), typeVarName)); + } else if (parentAST instanceof TStructMethod) { + correspondingTypeVarInAST = findFirst(((TStructMethod) parentAST) + .getTypeVars(), tv -> Objects.equals(tv.getName(), typeVarName)); + } else if (parentAST instanceof FunctionTypeExpression) { + correspondingTypeVarInAST = findFirst(((FunctionTypeExpression) parentAST) + .getOwnedTypeVars(), tv -> Objects.equals(tv.getName(), typeVarName)); + } + if (correspondingTypeVarInAST != null) { + return correspondingTypeVarInAST; + } + } + } + return element; + + } else if (element instanceof TFormalParameter && astElem == null) { + return element; + } else if (element instanceof TStructMember && astElem == null) { + return element; + } else if (element instanceof TMember && ((TMember) element).isComposed()) { + return element; + } else if (element instanceof TStructuralType) { + if (((TStructuralType) element).getName() == null && astElem == null) { + EObject parent = element.eContainer(); + return convertToSource(parent); + } + } else if (element instanceof SyntaxRelatedTElement) { + if (astElem == null) { + if (NodeModelUtils.getNode(element) != null) { + return element; + } + throw new IllegalStateException(); + } + return astElem; + } + return element; + } + + @Override + protected EStructuralFeature getIdentifierFeature(EObject obj) { + if (obj instanceof PropertyNameOwner) { + return N4JSPackage.eINSTANCE.getPropertyNameOwner_DeclaredName(); + } + return super.getIdentifierFeature(obj); + } + + @Override + protected ITextRegion getLocationOfContainmentReference(EObject owner, EReference feature, + int indexInList, RegionDescription query) { + + EObject referencedElement = null; + if (feature.isMany()) { + List values = (List) owner.eGet(feature); + if (indexInList >= values.size()) { + referencedElement = owner; + } else if (indexInList >= 0) { + referencedElement = (EObject) values.get(indexInList); + } + } else { + referencedElement = (EObject) owner.eGet(feature); + } + return getTextRegion(referencedElement, query); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSLocationInFileProvider.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSLocationInFileProvider.xtend deleted file mode 100644 index d30d5f9c8f..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSLocationInFileProvider.xtend +++ /dev/null @@ -1,196 +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.resource - -import java.util.Objects -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.EStructuralFeature -import org.eclipse.n4js.N4JSGlobals -import org.eclipse.n4js.n4JS.GenericDeclaration -import org.eclipse.n4js.n4JS.N4JSPackage -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.n4JS.PropertyNameOwner -import org.eclipse.n4js.n4JS.PropertyNameValuePair -import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression -import org.eclipse.n4js.ts.types.SyntaxRelatedTElement -import org.eclipse.n4js.ts.types.TFormalParameter -import org.eclipse.n4js.ts.types.TMember -import org.eclipse.n4js.ts.types.TStructMember -import org.eclipse.n4js.ts.types.TStructMethod -import org.eclipse.n4js.ts.types.TStructuralType -import org.eclipse.n4js.ts.types.TypeVariable -import org.eclipse.n4js.utils.URIUtils -import org.eclipse.n4js.xtext.resource.XITextRegionWithLineInformation -import org.eclipse.xtext.nodemodel.util.NodeModelUtils -import org.eclipse.xtext.resource.DefaultLocationInFileProvider -import org.eclipse.xtext.resource.ILocationInFileProviderExtension -import org.eclipse.xtext.util.ITextRegion -import org.eclipse.xtext.resource.ILocationInFileProviderExtension.RegionDescription -import java.util.List -import org.eclipse.emf.ecore.EReference - -/** - * A location in file provider that is aware of inferred types. The location - * of the referenced AST element is used instead. - */ -class N4JSLocationInFileProvider extends DefaultLocationInFileProvider { - - override getFullTextRegion(EObject element) { - val srcObj = convertToSource(element); - if (srcObj === null) { - return null; - } - return super.getFullTextRegion(srcObj); - } - - override getFullTextRegion(EObject owner, EStructuralFeature feature, int indexInList) { - val srcObj = convertToSource(owner); - if (srcObj === null) { - return null; - } - return super.getFullTextRegion(srcObj, feature, indexInList); - } - - override getSignificantTextRegion(EObject element) { - if(element instanceof NamedImportSpecifier) { - if(!element.isDefaultImport()) { - if(element.getAlias() !== null) { - return super.getSignificantTextRegion(element, N4JSPackage.eINSTANCE.getNamedImportSpecifier_Alias(), -1); - } - } - } else if(element instanceof NamespaceImportSpecifier) { - if (element.getAlias() !== null) { - return super.getSignificantTextRegion(element, N4JSPackage.eINSTANCE.getNamespaceImportSpecifier_Alias(), -1); - } - } - val srcObj = convertToSource(element); - if (srcObj === null) { - return null; - } - return super.getSignificantTextRegion(srcObj); - } - - override getSignificantTextRegion(EObject owner, EStructuralFeature feature, int indexInList) { - val srcObj = convertToSource(owner); - if (srcObj === null) { - return null; - } - return super.getSignificantTextRegion(srcObj, feature, indexInList); - } - - override getTextRegion(EObject object, EStructuralFeature feature, int indexInList, - ILocationInFileProviderExtension.RegionDescription query) { - - val srcObj = convertToSource(object); - if (srcObj === null) { - return null; - } - return super.getTextRegion(srcObj, feature, indexInList, query); - } - - override getTextRegion(EObject object, ILocationInFileProviderExtension.RegionDescription query) { - val srcObj = convertToSource(object); - if (srcObj === null) { - return null; - } - return super.getTextRegion(srcObj, query); - } - - override protected ITextRegion doGetTextRegion(EObject obj, /* @NonNull */ RegionDescription query) { - if (Objects.equals(N4JSGlobals.DTS_FILE_EXTENSION, URIUtils.fileExtension(obj.eResource?.URI))) { - for (adapter : obj.eAdapters) { - if (adapter instanceof XITextRegionWithLineInformation) { - return toZeroBasedRegion(adapter); - } - } - } else if (obj instanceof TMember && !(obj as TMember).constituentMembers.empty) { - val tMember = obj as TMember; - val fst = tMember.constituentMembers.get(0); - return super.doGetTextRegion(fst.astElement, query); - } else if (obj instanceof PropertyNameValuePair && (obj as PropertyNameValuePair).property !== null) { - return super.getFullTextRegion(obj, N4JSPackage.eINSTANCE.propertyNameValuePair_Property, 0); - } else { - return super.doGetTextRegion(obj, query); - } - } - - - // TODO use N4JSASTUtils#getCorrespondingASTNode() instead, if possible - def public EObject convertToSource(EObject element) { - if (element === null || element.eIsProxy) - return null; - switch (element) { - TypeVariable: { - val parentAST = convertToSource(element.eContainer); - if(parentAST instanceof GenericDeclaration || parentAST instanceof TStructMethod || parentAST instanceof FunctionTypeExpression) { - val typeVarName = element.name; - if(typeVarName!==null && typeVarName.trim.length>0) { - val correspondingTypeVarInAST = switch parentAST { - GenericDeclaration: parentAST.typeVars.findFirst[name==typeVarName] - TStructMethod: parentAST.typeVars.findFirst[name==typeVarName] - FunctionTypeExpression: parentAST.ownedTypeVars.findFirst[name==typeVarName] - }; - if(correspondingTypeVarInAST!==null) - return correspondingTypeVarInAST; - } - } - return element; - } - TFormalParameter case element.astElement === null: - element - TStructMember case element.astElement === null: - element - TMember case element.composed: - element - TStructuralType case element.name === null && element.astElement === null: { - val parent = element.eContainer - return convertToSource(parent) - } - SyntaxRelatedTElement: { - if (element.astElement === null) { - if (NodeModelUtils.getNode(element) !== null) { - return element; - } - throw new IllegalStateException() - } - element.astElement - } - default: - element - } - } - - override protected EStructuralFeature getIdentifierFeature(EObject obj) { - if(obj instanceof PropertyNameOwner) { - return N4JSPackage.eINSTANCE.propertyNameOwner_DeclaredName; - } - return super.getIdentifierFeature(obj); - } - - - override protected ITextRegion getLocationOfContainmentReference(EObject owner, EReference feature, - int indexInList, RegionDescription query) { - - var EObject referencedElement = null; - if (feature.isMany()) { - val List values = owner.eGet(feature) as List; - if (indexInList >= values.size()) { - referencedElement = owner; - } else if (indexInList >= 0) { - referencedElement = values.get(indexInList) as EObject; - } - } else { - referencedElement = owner.eGet(feature) as EObject; - } - return getTextRegion(referencedElement, query); - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSPreProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSPreProcessor.java new file mode 100644 index 0000000000..0da071af04 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSPreProcessor.java @@ -0,0 +1,132 @@ +/** + * 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.resource; + +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.toIterable; + +import java.math.BigDecimal; +import java.util.Objects; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope; +import org.eclipse.n4js.ts.typeRefs.BooleanLiteralTypeRef; +import org.eclipse.n4js.ts.typeRefs.EnumLiteralTypeRef; +import org.eclipse.n4js.ts.typeRefs.NumericLiteralTypeRef; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.StringLiteralTypeRef; +import org.eclipse.n4js.validation.ASTStructureValidator; + +import com.google.inject.Singleton; + +/** + * This class performs some early pre-processing of the AST. This happens after {@link N4JSLinker lazy linking} and + * before {@link ASTStructureValidator AST structure validation}. + */ +@Singleton +@SuppressWarnings("unused") +final class N4JSPreProcessor { + + /** + * Performs an early processing of the AST, e.g. initialization of transient helper values. This method assumes + * that it is allowed to change the AST! Thus, it should be invoked from an "exec without cache clear" handler, + * see {@code OnChangeEvictingCache#execWithoutCacheClear(N4JSResource,IUnitOfWork)}. + */ + public void process(Script script, N4JSResource resource) { + ResourceSet resourceSet = resource.getResourceSet(); + if (resourceSet == null) { + // null-safe exit - required in smoke test where Resources detached from a ResourceSet are used. + return; + } + BuiltInTypeScope builtInTypes = BuiltInTypeScope.get(resourceSet); + for (EObject node : toIterable(resource.getScript().eAllContents())) { + processNode(node, resource, builtInTypes); + } + } + + private void processNode(EObject astNode, N4JSResource resource, BuiltInTypeScope builtInTypes) { + if (astNode instanceof ParameterizedTypeRef) { + processNode((ParameterizedTypeRef) astNode, resource, builtInTypes); + } else if (astNode instanceof BooleanLiteralTypeRef) { + processNode((BooleanLiteralTypeRef) astNode, resource, builtInTypes); + } else if (astNode instanceof NumericLiteralTypeRef) { + processNode((NumericLiteralTypeRef) astNode, resource, builtInTypes); + } else if (astNode instanceof StringLiteralTypeRef) { + processNode((StringLiteralTypeRef) astNode, resource, builtInTypes); + } else if (astNode instanceof EnumLiteralTypeRef) { + processNode((EnumLiteralTypeRef) astNode, resource, builtInTypes); + } else { + // by default, do nothing + } + } + + /** + * Support for array type syntax: + * + *
+	 * let arr: string[];
+	 * 
+ * + * and arrrayN syntax: + * + *
+	 * let tup: [string, int];
+	 * 
+ * + * Note that both {@code string[]} and {@code [string]} results in an {@code Array} + */ + private void processNode(ParameterizedTypeRef typeRef, N4JSResource resource, BuiltInTypeScope builtInTypes) { + if (typeRef.isArrayTypeExpression()) { + typeRef.setDeclaredType(builtInTypes.getArrayType()); + } else if (typeRef.isArrayNTypeExpression()) { + int n = typeRef.getDeclaredTypeArgs().size(); + if (n < 2) { + typeRef.setDeclaredType(builtInTypes.getArrayType()); + } else if (n <= BuiltInTypeScope.ITERABLE_N__MAX_LEN) { + typeRef.setDeclaredType(builtInTypes.getArrayNType(n)); + } else { + // error (a validation will create an issue) + // NOTE: it would be nice to create an InterableN with a union as last type argument + // containing those element types that exceed the ITERABLE_N__MAX_LEN; however, this + // would require AST rewriting, which isn't allowed. + typeRef.setDeclaredType(builtInTypes.getArrayNType(BuiltInTypeScope.ITERABLE_N__MAX_LEN)); + } + } + } + + private void processNode(BooleanLiteralTypeRef typeRef, N4JSResource resource, BuiltInTypeScope builtInTypes) { + typeRef.setValue(Objects.equals(typeRef.getAstValue(), "true")); + } + + private void processNode(NumericLiteralTypeRef typeRef, N4JSResource resource, BuiltInTypeScope builtInTypes) { + BigDecimal valueRaw = (BigDecimal) typeRef.getAstValue(); // validity of this cast is enforced by the grammar + if (valueRaw != null) { + valueRaw = valueRaw.stripTrailingZeros(); + if (typeRef.isAstNegated()) { + valueRaw = valueRaw.negate(); + } + typeRef.setValue(valueRaw); + } else { + // syntax error + typeRef.setValue(BigDecimal.ZERO); + } + } + + private void processNode(StringLiteralTypeRef typeRef, N4JSResource resource, BuiltInTypeScope builtInTypes) { + typeRef.setValue((String) typeRef.getAstValue()); // validity of this cast is enforced by the grammar + } + + private void processNode(EnumLiteralTypeRef typeRef, N4JSResource resource, BuiltInTypeScope builtInTypes) { + // setting the value of an EnumLiteralTypeRef requires scoping and can therefore not be done here; + // see N4JSScopeProvider#getScopeByShortcut() and TypeRefProcessor#processEnumLiteralTypeRefs() + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSPreProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSPreProcessor.xtend deleted file mode 100644 index 89bc76d73f..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/N4JSPreProcessor.xtend +++ /dev/null @@ -1,109 +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.resource - -import com.google.inject.Singleton -import java.math.BigDecimal -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope -import org.eclipse.n4js.ts.typeRefs.BooleanLiteralTypeRef -import org.eclipse.n4js.ts.typeRefs.EnumLiteralTypeRef -import org.eclipse.n4js.ts.typeRefs.NumericLiteralTypeRef -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.StringLiteralTypeRef -import org.eclipse.n4js.validation.ASTStructureValidator - -/** - * This class performs some early pre-processing of the AST. This happens after {@link N4JSLinker lazy linking} and - * before {@link ASTStructureValidator AST structure validation}. - */ -@Singleton -package final class N4JSPreProcessor { - - /** - * Performs an early processing of the AST, e.g. initialization of transient helper values. - * This method assumes that it is allowed to change the AST! Thus, it should be invoked from an "exec without - * cache clear" handler, see {@code OnChangeEvictingCache#execWithoutCacheClear(N4JSResource,IUnitOfWork)}. - */ - def public void process(Script script, N4JSResource resource) { - val resourceSet = resource.resourceSet; - if( resourceSet === null ) { - // null-safe exit - required in smoke test where Resources detached from a ResourceSet are used. - return; - } - val builtInTypes = BuiltInTypeScope.get( resourceSet ); - for (node : resource.script.eAllContents.toIterable) { - processNode(node, resource, builtInTypes); - } - } - - def private dispatch void processNode(EObject astNode, N4JSResource resource, BuiltInTypeScope builtInTypes) { - // by default, do nothing - } - - /** - * Support for array type syntax: - *
-	 * let arr: string[];
-	 * 
- * and arrrayN syntax: - *
-	 * let tup: [string, int];
-	 * 
- * Note that both {@code string[]} and {@code [string]} results in an {@code Array} - */ - def private dispatch void processNode(ParameterizedTypeRef typeRef, N4JSResource resource, BuiltInTypeScope builtInTypes) { - if (typeRef.isArrayTypeExpression) { - typeRef.declaredType = builtInTypes.arrayType; - } else if (typeRef.isArrayNTypeExpression) { - val n = typeRef.declaredTypeArgs.size; - if (n < 2) { - typeRef.declaredType = builtInTypes.arrayType; - } else if (n <= BuiltInTypeScope.ITERABLE_N__MAX_LEN) { - typeRef.declaredType = builtInTypes.getArrayNType(n); - } else { - // error (a validation will create an issue) - // NOTE: it would be nice to create an InterableN with a union as last type argument - // containing those element types that exceed the ITERABLE_N__MAX_LEN; however, this - // would require AST rewriting, which isn't allowed. - typeRef.declaredType = builtInTypes.getArrayNType(BuiltInTypeScope.ITERABLE_N__MAX_LEN); - } - } - } - - def private dispatch void processNode(BooleanLiteralTypeRef typeRef, N4JSResource resource, BuiltInTypeScope builtInTypes) { - typeRef.value = typeRef.astValue == "true"; - } - - def private dispatch void processNode(NumericLiteralTypeRef typeRef, N4JSResource resource, BuiltInTypeScope builtInTypes) { - var valueRaw = typeRef.astValue as BigDecimal; // validity of this cast is enforced by the grammar - if (valueRaw !== null) { - valueRaw = valueRaw.stripTrailingZeros; - if (typeRef.astNegated) { - valueRaw = valueRaw.negate(); - } - typeRef.value = valueRaw; - } else { - // syntax error - typeRef.value = BigDecimal.ZERO; - } - } - - def private dispatch void processNode(StringLiteralTypeRef typeRef, N4JSResource resource, BuiltInTypeScope builtInTypes) { - typeRef.value = typeRef.astValue as String; // validity of this cast is enforced by the grammar - } - - def private dispatch void processNode(EnumLiteralTypeRef typeRef, N4JSResource resource, BuiltInTypeScope builtInTypes) { - // setting the value of an EnumLiteralTypeRef requires scoping and can therefore not be done here; - // see N4JSScopeProvider#getScopeByShortcut() and TypeRefProcessor#processEnumLiteralTypeRefs() - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/PackageJsonResourceDescriptionExtension.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/PackageJsonResourceDescriptionExtension.java new file mode 100644 index 0000000000..61074a98b5 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/PackageJsonResourceDescriptionExtension.java @@ -0,0 +1,380 @@ +/** + * Copyright (c) 2018 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.resource; + +import static com.google.common.base.Strings.nullToEmpty; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filterNull; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.isNullOrEmpty; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toSet; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.n4js.N4JSGlobals; +import org.eclipse.n4js.json.JSON.JSONDocument; +import org.eclipse.n4js.json.JSON.JSONPackage; +import org.eclipse.n4js.json.extension.IJSONResourceDescriptionExtension; +import org.eclipse.n4js.packagejson.PackageJsonUtils; +import org.eclipse.n4js.packagejson.projectDescription.ProjectDependency; +import org.eclipse.n4js.packagejson.projectDescription.ProjectDescription; +import org.eclipse.n4js.packagejson.projectDescription.ProjectReference; +import org.eclipse.n4js.packagejson.projectDescription.ProjectType; +import org.eclipse.n4js.semver.Semver.VersionNumber; +import org.eclipse.n4js.semver.model.SemverSerializer; +import org.eclipse.n4js.utils.ProjectDescriptionLoader; +import org.eclipse.n4js.workspace.locations.FileURI; +import org.eclipse.n4js.workspace.utils.SemanticDependencySupplier; +import org.eclipse.xtext.naming.IQualifiedNameProvider; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.resource.EObjectDescription; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.resource.IResourceDescription; +import org.eclipse.xtext.resource.IResourceDescription.Delta; +import org.eclipse.xtext.resource.IResourceDescriptions; +import org.eclipse.xtext.util.IAcceptor; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.inject.Inject; + +/** + * {@link IJSONResourceDescriptionExtension} implementation that provides custom resource descriptions of + * {@code package.json} resources. + */ +public class PackageJsonResourceDescriptionExtension implements IJSONResourceDescriptionExtension { + + /** + * Separator that is used to serialize multiple project identifiers as a string. + */ + private static String SEPARATOR = ";"; + + /** The key of the user data for retrieving the project name. */ + public static String PROJECT_NAME_KEY = "projectName"; + + /** The key of the user data for retrieving the project version. */ + public static String PROJECT_VERSION_KEY = "projectVersion"; + + /** The key of the user data for retrieving the project type as a string. */ + private static String PROJECT_TYPE_KEY = "projectType"; + + /** Key for the project implementation ID value. {@code null} value will be mapped to empty string. */ + private static String IMPLEMENTATION_ID_KEY = "implementationId"; + + /** + * Key for storing the test project names. If a project does not have any tested projects this key will be missing + * from the user data. The values are separated with the {@link PackageJsonResourceDescriptionExtension#SEPARATOR} + * character. + */ + private static String TESTED_PROJECT_NAMES_KEY = "testedProjectNames"; + + /** + * Key for storing the implemented project names. If a project does not implement any projects this key will be + * missing from the user data. The values are separated with the + * {@link PackageJsonResourceDescriptionExtension#SEPARATOR} character. + */ + private static String IMPLEMENTED_PROJECT_NAMES_KEY = "implementedProjectNames"; + + /** + * Key for storing the project names of all direct dependencies. If a project does not have any direct projects this + * key will be missing from the user data. The values are separated with the + * {@link PackageJsonResourceDescriptionExtension#SEPARATOR} character. + */ + private static String PROJECT_DEPENDENCY_NAMES_KEY = "projectDependencyNames"; + + /** + * Key for storing the project names of all provided runtime libraries. If the project does not provide any runtime + * libraries, then this value will be omitted form the user data. Multiple values are separated with the + * {@link PackageJsonResourceDescriptionExtension#SEPARATOR} character. + */ + private static String PROVIDED_RUNTIME_LIBRARY_NAMES_KEY = "providedRuntimeLibraryNames"; + + /** + * Key for storing the project names of all required runtime libraries. If the project does not have any runtime + * library requirement, this value will not be present in the user data. Multiple values will be joined with the + * {@link PackageJsonResourceDescriptionExtension#SEPARATOR} separator. + */ + private static String REQUIRED_RUNTIME_LIBRARY_NAMES_KEY = "requiredRuntimeLibraryNames"; + + /** + * Key for storing the unique project identifier of the extended runtime environment. If the project does not extend + * any runtime environment, then this value will not exist in the user data. + */ + private static String EXTENDED_RUNTIME_ENVIRONMENT_NAME_KEY = "extendedRuntimeEnvironmentName"; + + @Inject + private IQualifiedNameProvider qualifiedNameProvider; + + @Inject + private ProjectDescriptionLoader projectDescriptionLoader; + + private static final Logger LOGGER = Logger.getLogger(PackageJsonResourceDescriptionExtension.class); + + @Override + public QualifiedName getFullyQualifiedName(EObject obj) { + if (!isPackageJSON(obj)) { + return null; // not responsible + } + + // delegate to the N4JS qualified name provider + // (will return non-null only for JSONDocument, i.e. the root AST node in JSON files) + return qualifiedNameProvider.getFullyQualifiedName(obj); + } + + @Override + public boolean isAffected(Collection deltas, IResourceDescription candidate, IResourceDescriptions context) { + if (!isPackageJSON(candidate)) { + return false; // not responsible + } + + Set changedProjectNames = new LinkedHashSet<>(); + + for (Delta delta : deltas) { + if ((delta.getNew() == null || delta.getOld() == null || delta.haveEObjectDescriptionsChanged()) + && isPackageJSON(delta.getUri())) { + + Iterable exportedObjects = ((delta.getNew() == null) + ? delta.getOld() + : delta.getNew()).getExportedObjects(); + + Iterator iter = exportedObjects.iterator(); + if (iter.hasNext()) { + String projectName = getProjectName(iter.next()); + String packageName = SemanticDependencySupplier.convertProjectIdToPackageName(projectName); + changedProjectNames.add(packageName); + } + } + } + + // Collect all referenced project IDs of the candidate. + List referencedProjectNames = new LinkedList<>(); + for (IEObjectDescription od : candidate.getExportedObjectsByType(JSONPackage.Literals.JSON_DOCUMENT)) { + if (getProjectType(od) == ProjectType.PLAINJS) { + // never rebuild plain-js projects on incremental builds + // return false; + } + referencedProjectNames.addAll(getTestedProjectNames(od)); + referencedProjectNames.addAll(getImplementedProjectNames(od)); + referencedProjectNames.addAll(getProjectDependencyNames(od)); + referencedProjectNames.addAll(getProvidedRuntimeLibraryNames(od)); + referencedProjectNames.addAll(getRequiredRuntimeLibraryNames(od)); + String extRuntimeEnvironmentId = getExtendedRuntimeEnvironmentName(od); + if (!Strings.isNullOrEmpty(extRuntimeEnvironmentId)) { + referencedProjectNames.add(extRuntimeEnvironmentId); + } + } + + // Here we consider only direct project dependencies because this implementation is aligned to the + // N4JS based resource description manager's #isAffected logic. In the N4JS implementation we consider + // only direct project dependencies when checking whether a candidate is affected or not. + // + // See: N4JSResourceDescriptionManager#basicIsAffected and N4JSResourceDescriptionManager#hasDependencyTo + for (String referencedProjectName : referencedProjectNames) { + if (changedProjectNames.contains(referencedProjectName)) { + return true; + } + } + + return false; + } + + @Override + public void createJSONDocumentDescriptions(JSONDocument document, IAcceptor acceptor) { + QualifiedName qualifiedName = getFullyQualifiedName(document); + if (qualifiedName == null) { + return; // not responsible + } + + URI projectLocation = document == null || document.eResource() == null || document.eResource().getURI() == null + ? null + : document.eResource().getURI().trimSegments(1); + if (projectLocation == null) { + LOGGER.error("creation of EObjectDescriptions failed: cannot derive project location from document"); + return; + } + ProjectDescription description = projectDescriptionLoader + .loadProjectDescriptionAtLocation(new FileURI(projectLocation), null, document); + if (description == null) { + // this can happen when package.json files are opened that do not belong to a valid N4JS or PLAINJS project + // (maybe during manual creation of a new project); therefore we cannot log an error here: + // LOGGER.error("creation of EObjectDescriptions failed: cannot load project description at location: " + + // projectLocation); + return; + } + Map userData = createProjectDescriptionUserData(description); + acceptor.accept(new EObjectDescription(qualifiedName, document, userData)); + } + + /** + * Creates the user data of a {@link ProjectDescription} {@link IEObjectDescription}. + */ + private Map createProjectDescriptionUserData(ProjectDescription pd) { + Builder builder = ImmutableMap.builder(); + builder.put(PROJECT_TYPE_KEY, PackageJsonUtils.getProjectTypeStringRepresentation(pd.getProjectType())); + builder.put(PROJECT_NAME_KEY, nullToEmpty(pd.getId())); + builder.put(IMPLEMENTATION_ID_KEY, nullToEmpty(pd.getImplementationId())); + + VersionNumber vers = pd.getVersion(); + if (vers != null) { + String versionStr = SemverSerializer.serialize(vers); + builder.put(PROJECT_VERSION_KEY, versionStr); + } + + List testedProjects = pd.getTestedProjects(); + if (!isNullOrEmpty(testedProjects)) { + builder.put(PackageJsonResourceDescriptionExtension.TESTED_PROJECT_NAMES_KEY, asString(testedProjects)); + } + + List implementedProjects = pd.getImplementedProjects(); + if (!isNullOrEmpty(implementedProjects)) { + builder.put(IMPLEMENTED_PROJECT_NAMES_KEY, asString(implementedProjects)); + } + + List projectDependencies = pd.getProjectDependencies(); + if (!isNullOrEmpty(projectDependencies)) { + builder.put(PROJECT_DEPENDENCY_NAMES_KEY, asString(projectDependencies)); + } + + List providedRuntimeLibraries = pd.getProvidedRuntimeLibraries(); + if (!isNullOrEmpty(providedRuntimeLibraries)) { + builder.put(PROVIDED_RUNTIME_LIBRARY_NAMES_KEY, asString(providedRuntimeLibraries)); + } + + List requiredRuntimeLibraries = pd.getRequiredRuntimeLibraries(); + if (!isNullOrEmpty(requiredRuntimeLibraries)) { + builder.put(REQUIRED_RUNTIME_LIBRARY_NAMES_KEY, asString(requiredRuntimeLibraries)); + } + + ProjectReference extRuntimeEnvironment = pd.getExtendedRuntimeEnvironment(); + if (extRuntimeEnvironment != null) { + builder.put(EXTENDED_RUNTIME_ENVIRONMENT_NAME_KEY, + asString(Collections.singleton(pd.getExtendedRuntimeEnvironment()))); + } + + return builder.build(); + } + + /** + * Optionally returns with the project type extracted from the user data of the given EObject description argument. + */ + public static ProjectType getProjectType(IEObjectDescription it) { + if (it == null) { + return null; + } + String projectTypeStr = it.getUserData(PROJECT_TYPE_KEY); + if (projectTypeStr == null) { + return null; + } + return PackageJsonUtils.parseProjectType(projectTypeStr); + } + + /** + * Optionally returns with the project name extracted from the user data of the given EObject description argument. + */ + public static String getProjectName(IEObjectDescription it) { + if (it == null) { + return null; + } + return it.getUserData(PROJECT_NAME_KEY); + } + + /** + * Returns with a collection of distinct IDs of the tested projects. Never returns with {@code null}. + */ + public static Set getTestedProjectNames(IEObjectDescription od) { + return getProjectNamesUserDataOf(od, PackageJsonResourceDescriptionExtension.TESTED_PROJECT_NAMES_KEY); + } + + /** + * Returns with a collection of distinct IDs of the implemented projects. Never returns with {@code null}. + */ + public static Set getImplementedProjectNames(IEObjectDescription od) { + return getProjectNamesUserDataOf(od, IMPLEMENTED_PROJECT_NAMES_KEY); + } + + /** + * Returns with a collection of distinct IDs of the project dependencies. Never returns with {@code null}. + */ + public static Set getProjectDependencyNames(IEObjectDescription od) { + return getProjectNamesUserDataOf(od, PROJECT_DEPENDENCY_NAMES_KEY); + } + + /** + * Returns with a collection of distinct IDs of the provided runtime libraries. Never returns with {@code null}. + */ + public static Set getProvidedRuntimeLibraryNames(IEObjectDescription od) { + return getProjectNamesUserDataOf(od, PROVIDED_RUNTIME_LIBRARY_NAMES_KEY); + } + + /** + * Returns with a collection of distinct IDs of the required runtime libraries. Never returns with {@code null}. + */ + public static Set getRequiredRuntimeLibraryNames(IEObjectDescription od) { + return getProjectNamesUserDataOf(od, REQUIRED_RUNTIME_LIBRARY_NAMES_KEY); + } + + /** + * Returns with the ID of the extended runtime environment. May return with {@code null} if argument is {@code null} + * or if the value of the user data key is {@code null}. In a nutshell, if a project does not extend a RE. + */ + public static String getExtendedRuntimeEnvironmentName(IEObjectDescription od) { + if (od == null) { + return null; + } + return od.getUserData(EXTENDED_RUNTIME_ENVIRONMENT_NAME_KEY); + } + + /** + * Returns with a collection of distinct project IDs extracted from the user data. Never returns with {@code null}. + */ + private static Set getProjectNamesUserDataOf(IEObjectDescription it, String key) { + if (it == null) { + return Collections.emptySet(); + } + return toSet(filter(Arrays.asList(nullToEmpty(it.getUserData(key)).split(SEPARATOR)), + str -> !Strings.isNullOrEmpty(str))); + } + + private static String asString(Iterable it) { + return org.eclipse.n4js.utils.Strings.join(SEPARATOR, + filterNull(map(filterNull(it), pr -> pr.getPackageName()))); + } + + private static boolean isPackageJSON(IResourceDescription desc) { + return desc != null && isPackageJSON(desc.getURI()); + } + + private static boolean isPackageJSON(EObject obj) { + return obj != null && isPackageJSON(obj.eResource()); + } + + private static boolean isPackageJSON(Resource res) { + return res != null && isPackageJSON(res.getURI()); + } + + private static boolean isPackageJSON(URI uri) { + return uri != null && Objects.equals(uri.lastSegment(), N4JSGlobals.PACKAGE_JSON); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/PackageJsonResourceDescriptionExtension.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/PackageJsonResourceDescriptionExtension.xtend deleted file mode 100644 index 4e812d1c84..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/PackageJsonResourceDescriptionExtension.xtend +++ /dev/null @@ -1,344 +0,0 @@ -/** - * Copyright (c) 2018 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.resource - -import com.google.common.collect.ImmutableMap -import com.google.inject.Inject -import java.util.Collection -import java.util.Collections -import java.util.Map -import org.apache.log4j.Logger -import org.eclipse.emf.common.util.URI -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.n4js.N4JSGlobals -import org.eclipse.n4js.json.JSON.JSONDocument -import org.eclipse.n4js.json.JSON.JSONPackage -import org.eclipse.n4js.json.^extension.IJSONResourceDescriptionExtension -import org.eclipse.n4js.packagejson.PackageJsonUtils -import org.eclipse.n4js.packagejson.projectDescription.ProjectDescription -import org.eclipse.n4js.packagejson.projectDescription.ProjectReference -import org.eclipse.n4js.semver.model.SemverSerializer -import org.eclipse.n4js.utils.ProjectDescriptionLoader -import org.eclipse.n4js.workspace.locations.FileURI -import org.eclipse.n4js.workspace.utils.SemanticDependencySupplier -import org.eclipse.xtext.naming.IQualifiedNameProvider -import org.eclipse.xtext.naming.QualifiedName -import org.eclipse.xtext.resource.EObjectDescription -import org.eclipse.xtext.resource.IEObjectDescription -import org.eclipse.xtext.resource.IResourceDescription -import org.eclipse.xtext.resource.IResourceDescription.Delta -import org.eclipse.xtext.resource.IResourceDescriptions -import org.eclipse.xtext.util.IAcceptor - -import static extension com.google.common.base.Strings.nullToEmpty -import org.eclipse.n4js.packagejson.projectDescription.ProjectType - -/** - * {@link IJSONResourceDescriptionExtension} implementation that provides custom resource descriptions of - * {@code package.json} resources. - */ -class PackageJsonResourceDescriptionExtension implements IJSONResourceDescriptionExtension { - - /** - * Separator that is used to serialize multiple project identifiers as a string. - */ - private static val SEPARATOR = ";"; - - /** The key of the user data for retrieving the project name. */ - public static val PROJECT_NAME_KEY = 'projectName'; - - /** The key of the user data for retrieving the project version. */ - public static val PROJECT_VERSION_KEY = 'projectVersion'; - - /** The key of the user data for retrieving the project type as a string. */ - private static val PROJECT_TYPE_KEY = 'projectType'; - - /** Key for the project implementation ID value. {@code null} value will be mapped to empty string. */ - private static val IMPLEMENTATION_ID_KEY = 'implementationId'; - - /** - * Key for storing the test project names. - * If a project does not have any tested projects this key will be missing from the user data. - * The values are separated with the {@link PackageJsonResourceDescriptionExtension#SEPARATOR} character. - */ - private static val TESTED_PROJECT_NAMES_KEY = 'testedProjectNames'; - - /** - * Key for storing the implemented project names. - * If a project does not implement any projects this key will be missing from the user data. - * The values are separated with the {@link PackageJsonResourceDescriptionExtension#SEPARATOR} character. - */ - private static val IMPLEMENTED_PROJECT_NAMES_KEY = 'implementedProjectNames'; - - /** - * Key for storing the project names of all direct dependencies. - * If a project does not have any direct projects this key will be missing from the user data. - * The values are separated with the {@link PackageJsonResourceDescriptionExtension#SEPARATOR} character. - */ - private static val PROJECT_DEPENDENCY_NAMES_KEY = 'projectDependencyNames'; - - /** - * Key for storing the project names of all provided runtime libraries. - * If the project does not provide any runtime libraries, then this value will be omitted form the user data. - * Multiple values are separated with the {@link PackageJsonResourceDescriptionExtension#SEPARATOR} character. - */ - private static val PROVIDED_RUNTIME_LIBRARY_NAMES_KEY = 'providedRuntimeLibraryNames'; - - /** - * Key for storing the project names of all required runtime libraries. - * If the project does not have any runtime library requirement, this value will not be present in the user data. - * Multiple values will be joined with the {@link PackageJsonResourceDescriptionExtension#SEPARATOR} separator. - */ - private static val REQUIRED_RUNTIME_LIBRARY_NAMES_KEY = 'requiredRuntimeLibraryNames'; - - /** - * Key for storing the unique project identifier of the extended runtime environment. If the project does not - * extend any runtime environment, then this value will not exist in the user data. - */ - private static val EXTENDED_RUNTIME_ENVIRONMENT_NAME_KEY = 'extendedRuntimeEnvironmentName'; - - @Inject - private IQualifiedNameProvider qualifiedNameProvider; - - @Inject - private ProjectDescriptionLoader projectDescriptionLoader; - - private static final Logger LOGGER = Logger.getLogger(PackageJsonResourceDescriptionExtension); - - - override QualifiedName getFullyQualifiedName(EObject obj) { - if (!obj.isPackageJSON) { - return null; // not responsible - } - - // delegate to the N4JS qualified name provider - // (will return non-null only for JSONDocument, i.e. the root AST node in JSON files) - return qualifiedNameProvider.getFullyQualifiedName(obj); - } - - - override boolean isAffected(Collection deltas, IResourceDescription candidate, IResourceDescriptions context) { - if (!candidate.isPackageJSON) { - return false; // not responsible - } - - val changedProjectNames = deltas - .filter[(it.getNew === null || it.getOld === null || it.haveEObjectDescriptionsChanged) && it.uri.isPackageJSON] - .map[(if (it.getNew === null) it.old else it.getNew).exportedObjects] - .filter[!it.empty] - .map[it.get(0).getProjectName] - .map[SemanticDependencySupplier.convertProjectIdToPackageName(it)] - .toSet; - - - // Collect all referenced project IDs of the candidate. - val referencedProjectNames = newLinkedList; - for (it : candidate.getExportedObjectsByType(JSONPackage.Literals.JSON_DOCUMENT)) { - if (getProjectType === ProjectType.PLAINJS) { - // never rebuild plain-js projects on incremental builds - // return false; - } - referencedProjectNames.addAll(testedProjectNames); - referencedProjectNames.addAll(implementedProjectNames); - referencedProjectNames.addAll(projectDependencyNames); - referencedProjectNames.addAll(providedRuntimeLibraryNames); - referencedProjectNames.addAll(requiredRuntimeLibraryNames); - val extRuntimeEnvironmentId = extendedRuntimeEnvironmentName; - if (!extRuntimeEnvironmentId.nullOrEmpty) { - referencedProjectNames.add(extRuntimeEnvironmentId); - } - } - - // Here we consider only direct project dependencies because this implementation is aligned to the - // N4JS based resource description manager's #isAffected logic. In the N4JS implementation we consider - // only direct project dependencies when checking whether a candidate is affected or not. - // - // See: N4JSResourceDescriptionManager#basicIsAffected and N4JSResourceDescriptionManager#hasDependencyTo - for (referencedProjectName : referencedProjectNames) { - if (changedProjectNames.contains(referencedProjectName)) { - return true; - } - } - - return false; - } - - override void createJSONDocumentDescriptions(JSONDocument document, IAcceptor acceptor) { - val qualifiedName = getFullyQualifiedName(document); - if (qualifiedName === null) { - return; // not responsible - } - - val projectLocation = document?.eResource?.URI?.trimSegments(1); - if(projectLocation === null) { - LOGGER.error("creation of EObjectDescriptions failed: cannot derive project location from document"); - return; - } - val description = projectDescriptionLoader.loadProjectDescriptionAtLocation(new FileURI(projectLocation), null, document); - if(description === null) { - // this can happen when package.json files are opened that do not belong to a valid N4JS or PLAINJS project - // (maybe during manual creation of a new project); therefore we cannot log an error here: - // LOGGER.error("creation of EObjectDescriptions failed: cannot load project description at location: " + projectLocation); - return; - } - val userData = createProjectDescriptionUserData(description); - acceptor.accept(new EObjectDescription(qualifiedName, document, userData)); - } - - /** - * Creates the user data of a {@link ProjectDescription} {@link IEObjectDescription}. - */ - private def Map createProjectDescriptionUserData(ProjectDescription it) { - val builder = ImmutableMap.builder; - builder.put(PROJECT_TYPE_KEY, '''«PackageJsonUtils.getProjectTypeStringRepresentation(getProjectType)»'''); - builder.put(PROJECT_NAME_KEY, id.nullToEmpty); - builder.put(IMPLEMENTATION_ID_KEY, implementationId.nullToEmpty); - - val vers = getVersion; - if (vers !== null) { - val versionStr = SemverSerializer.serialize(vers); - builder.put(PROJECT_VERSION_KEY, versionStr); - } - - val testedProjects = it.testedProjects; - if (!testedProjects.nullOrEmpty) { - builder.put(PackageJsonResourceDescriptionExtension.TESTED_PROJECT_NAMES_KEY, testedProjects.asString); - } - - val implementedProjects = it.implementedProjects; - if (!implementedProjects.nullOrEmpty) { - builder.put(IMPLEMENTED_PROJECT_NAMES_KEY, implementedProjects.asString); - } - - val projectDependencies = it.projectDependencies; - if (!projectDependencies.nullOrEmpty) { - builder.put(PROJECT_DEPENDENCY_NAMES_KEY, projectDependencies.asString); - } - - val providedRuntimeLibraries = providedRuntimeLibraries; - if (!providedRuntimeLibraries.nullOrEmpty) { - builder.put(PROVIDED_RUNTIME_LIBRARY_NAMES_KEY, providedRuntimeLibraries.asString); - } - - val requiredRuntimeLibraries = requiredRuntimeLibraries; - if (!requiredRuntimeLibraries.nullOrEmpty) { - builder.put(REQUIRED_RUNTIME_LIBRARY_NAMES_KEY, requiredRuntimeLibraries.asString); - } - - val extRuntimeEnvironment = it.extendedRuntimeEnvironment; - if (extRuntimeEnvironment !== null) { - builder.put(EXTENDED_RUNTIME_ENVIRONMENT_NAME_KEY, Collections.singleton(it.extendedRuntimeEnvironment).asString); - } - - return builder.build; - } - - /** - * Optionally returns with the project type extracted from the user data of the given EObject description argument. - */ - public static def getProjectType(IEObjectDescription it) { - if (it === null) { - return null; - } - val projectTypeStr = it.getUserData(PROJECT_TYPE_KEY); - if (projectTypeStr === null) { - return null; - } - return PackageJsonUtils.parseProjectType(projectTypeStr); - } - - /** - * Optionally returns with the project name extracted from the user data of the given EObject description argument. - */ - public static def getProjectName(IEObjectDescription it) { - if (it === null) { - return null; - } - return it.getUserData(PROJECT_NAME_KEY); - } - - /** - * Returns with a collection of distinct IDs of the tested projects. Never returns with {@code null}. - */ - public static def getTestedProjectNames(IEObjectDescription it) { - return getProjectNamesUserDataOf(PackageJsonResourceDescriptionExtension.TESTED_PROJECT_NAMES_KEY); - } - - /** - * Returns with a collection of distinct IDs of the implemented projects. Never returns with {@code null}. - */ - public static def getImplementedProjectNames(IEObjectDescription it) { - return getProjectNamesUserDataOf(IMPLEMENTED_PROJECT_NAMES_KEY); - } - - /** - * Returns with a collection of distinct IDs of the project dependencies. Never returns with {@code null}. - */ - public static def getProjectDependencyNames(IEObjectDescription it) { - return getProjectNamesUserDataOf(PROJECT_DEPENDENCY_NAMES_KEY); - } - - /** - * Returns with a collection of distinct IDs of the provided runtime libraries. Never returns with {@code null}. - */ - public static def getProvidedRuntimeLibraryNames(IEObjectDescription it) { - return getProjectNamesUserDataOf(PROVIDED_RUNTIME_LIBRARY_NAMES_KEY); - } - - /** - * Returns with a collection of distinct IDs of the required runtime libraries. Never returns with {@code null}. - */ - public static def getRequiredRuntimeLibraryNames(IEObjectDescription it) { - return getProjectNamesUserDataOf(REQUIRED_RUNTIME_LIBRARY_NAMES_KEY); - } - - /** - * Returns with the ID of the extended runtime environment. May return with {@code null} if argument is {@code null} - * or if the value of the user data key is {@code null}. In a nutshell, if a project does not extend a RE. - */ - public static def getExtendedRuntimeEnvironmentName(IEObjectDescription it) { - if (it === null) { - return null; - } - return it.getUserData(EXTENDED_RUNTIME_ENVIRONMENT_NAME_KEY); - } - - /** - * Returns with a collection of distinct project IDs extracted from the user data. Never returns with {@code null}. - */ - private static def getProjectNamesUserDataOf(IEObjectDescription it, String key) { - if (it === null) { - return emptySet; - } - return it.getUserData(key).nullToEmpty.split(SEPARATOR).filter[!nullOrEmpty].toSet; - } - - private static def String asString(Iterable it) { - it.filterNull.map[getPackageName].filterNull.join(SEPARATOR) - } - - private static def boolean isPackageJSON(IResourceDescription desc) { - return desc !== null && isPackageJSON(desc.URI); - } - - private static def boolean isPackageJSON(EObject obj) { - return obj !== null && isPackageJSON(obj.eResource); - } - - private static def boolean isPackageJSON(Resource res) { - return res !== null && isPackageJSON(res.URI); - } - - private static def boolean isPackageJSON(URI uri) { - return uri !== null && uri.lastSegment == N4JSGlobals.PACKAGE_JSON; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/XpectAwareFileExtensionCalculator.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/XpectAwareFileExtensionCalculator.java new file mode 100644 index 0000000000..1f37ad8e47 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/XpectAwareFileExtensionCalculator.java @@ -0,0 +1,97 @@ +/** + * 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.resource; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.impl.MinimalEObjectImpl; +import org.eclipse.n4js.N4JSGlobals; +import org.eclipse.n4js.utils.URIUtils; + +import com.google.common.base.Objects; +import com.google.common.base.Strings; +import com.google.inject.Singleton; + +/** + * This class provides methods for calculating file extensions. The calculation takes into account Xpect file extension + * {@code .xt}. Custom file Xpect file extensions are not supported e.g. when your Xpect tests are configured to run + * with {@code .xt} file extension. Deeply nested structures are also not supported e.g. using {@code file.n4js.xt.xt}. + */ +@Singleton +public class XpectAwareFileExtensionCalculator { + + /***/ + public String getXpectAwareFileExtension(EObject eob) { + URI uri = getURI(eob); + return getXpectAwareFileExtension(uri); + } + + private URI getURI(EObject eob) { + if (eob == null) { + return null; + } + if (eob.eResource() != null) { + return eob.eResource().getURI(); + } + if (eob instanceof MinimalEObjectImpl) { + return ((MinimalEObjectImpl) eob).eProxyURI().trimFragment(); + } + return null; + } + + /** + * Return the file extension of an URI + */ + public String getXpectAwareFileExtension(URI uri) { + if (uri == null) { + return ""; + } + return getXpectAwareFileExtensionOrEmpty(uri); + } + + /** + * Returns the name of the file that is referenced by {@code uri} without the potential additional X!PECT file + * extension. + */ + public String getFilenameWithoutXpectExtension(URI uri) { + if (!Objects.equal(URIUtils.fileExtension(uri), getXpectAwareFileExtension(uri))) { + return URIUtils.trimFileExtension(uri).lastSegment(); + } else { + return uri.lastSegment(); + } + } + + /** + * Returns the URI of the given EObject but without the Xpect file extension in case that is present. + */ + public URI getUriWithoutXpectExtension(EObject eob) { + URI uri = getURI(eob); + return getUriWithoutXpectExtension(uri); + } + + /** + * Removes the Xpect file extension in case that is present of the given URI. + */ + public URI getUriWithoutXpectExtension(URI uri) { + String fileName = getFilenameWithoutXpectExtension(uri); + URI uriWithoutXpectExtension = uri.trimSegments(1).appendSegment(fileName); + return uriWithoutXpectExtension; + } + + private String getXpectAwareFileExtensionOrEmpty(URI uri) { + String ext = URIUtils.fileExtension(uri); + if (N4JSGlobals.XT_FILE_EXTENSION.equals(ext)) { + // get nested file ext + ext = URIUtils.fileExtension(uri.trimFileExtension()); + } + return Strings.nullToEmpty(ext); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/XpectAwareFileExtensionCalculator.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/XpectAwareFileExtensionCalculator.xtend deleted file mode 100644 index 93f1ba5d6e..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/resource/XpectAwareFileExtensionCalculator.xtend +++ /dev/null @@ -1,95 +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.resource - -import com.google.common.base.Strings -import com.google.inject.Singleton -import org.eclipse.emf.common.util.URI -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.impl.MinimalEObjectImpl -import org.eclipse.n4js.N4JSGlobals -import org.eclipse.n4js.utils.URIUtils - -/** - * This class provides methods for calculating file extensions. The calculation takes into account Xpect file extension - * {@code .xt}. Custom file Xpect file extensions are not supported e.g. when your Xpect tests are configured to run with {@code .xxt} file extension. - * Deeply nested structures are also not supported e.g. using {@code file.n4js.xt.xt}. - */ - @Singleton -public class XpectAwareFileExtensionCalculator { - - def public String getXpectAwareFileExtension(EObject eob) { - val uri = getURI(eob); - return getXpectAwareFileExtension(uri); - } - - def private URI getURI(EObject eob) { - if (eob === null) { - return null; - } - if (eob.eResource !== null) { - return eob.eResource.URI; - } - if (eob instanceof MinimalEObjectImpl) { - return eob.eProxyURI.trimFragment; - } - return null; - } - - /** - * Return the file extension of an URI - */ - def public String getXpectAwareFileExtension(URI uri) { - if (uri === null) { - return ""; - } - return getXpectAwareFileExtensionOrEmpty(uri) - } - - /** - * Returns the name of the file that is referenced by {@code uri} - * without the potential additional X!PECT file extension. - */ - def public String getFilenameWithoutXpectExtension(URI uri) { - if (URIUtils.fileExtension(uri) != getXpectAwareFileExtension(uri)) { - return URIUtils.trimFileExtension(uri).lastSegment; - } else { - return uri.lastSegment; - } - } - - /** - * Returns the URI of the given EObject but without the Xpect file - * extension in case that is present. - */ - def public URI getUriWithoutXpectExtension(EObject eob) { - val uri = getURI(eob); - return getUriWithoutXpectExtension(uri); - } - - /** - * Removes the Xpect file extension in case that is present of the given URI. - */ - def public URI getUriWithoutXpectExtension(URI uri) { - val fileName = getFilenameWithoutXpectExtension(uri); - val uriWithoutXpectExtension = uri.trimSegments(1).appendSegment(fileName); - return uriWithoutXpectExtension; - } - - def private String getXpectAwareFileExtensionOrEmpty(URI uri){ - var ext = URIUtils.fileExtension(uri); - if(N4JSGlobals.XT_FILE_EXTENSION.equals(ext)){ - //get nested file ext - ext = URIUtils.fileExtension(uri.trimFileExtension) - } - return Strings.nullToEmpty(ext) - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/N4JSScopeProvider.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/N4JSScopeProvider.java new file mode 100644 index 0000000000..054978a157 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/N4JSScopeProvider.java @@ -0,0 +1,1121 @@ +/** + * 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.scoping; + +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.isIterableN; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.newRuleEnvironment; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.flatten; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.last; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.toIterable; +import static org.eclipse.xtext.xbase.lib.ListExtensions.reverseView; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.InternalEObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.n4js.n4JS.Argument; +import org.eclipse.n4js.n4JS.ArrayBindingPattern; +import org.eclipse.n4js.n4JS.BindingElement; +import org.eclipse.n4js.n4JS.BindingProperty; +import org.eclipse.n4js.n4JS.DestructNode; +import org.eclipse.n4js.n4JS.DestructureUtils; +import org.eclipse.n4js.n4JS.ExportDeclaration; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.JSXElement; +import org.eclipse.n4js.n4JS.JSXElementName; +import org.eclipse.n4js.n4JS.JSXPropertyAttribute; +import org.eclipse.n4js.n4JS.LabelledStatement; +import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName; +import org.eclipse.n4js.n4JS.MemberAccess; +import org.eclipse.n4js.n4JS.ModuleRef; +import org.eclipse.n4js.n4JS.N4ClassifierDeclaration; +import org.eclipse.n4js.n4JS.N4ClassifierDefinition; +import org.eclipse.n4js.n4JS.N4FieldAccessor; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.n4JS.N4MemberDeclaration; +import org.eclipse.n4js.n4JS.N4NamespaceDeclaration; +import org.eclipse.n4js.n4JS.N4TypeDeclaration; +import org.eclipse.n4js.n4JS.NamedExportSpecifier; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NewExpression; +import org.eclipse.n4js.n4JS.ObjectLiteral; +import org.eclipse.n4js.n4JS.ParameterizedCallExpression; +import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression; +import org.eclipse.n4js.n4JS.PropertyNameValuePair; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.n4JS.Statement; +import org.eclipse.n4js.n4JS.TypeDefiningElement; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.VariableEnvironmentElement; +import org.eclipse.n4js.resource.N4JSCache; +import org.eclipse.n4js.resource.N4JSResource; +import org.eclipse.n4js.scoping.accessModifiers.ContextAwareTypeScope; +import org.eclipse.n4js.scoping.accessModifiers.MemberVisibilityChecker; +import org.eclipse.n4js.scoping.imports.ImportedElementsScopingHelper; +import org.eclipse.n4js.scoping.imports.N4JSImportedNamespaceAwareLocalScopeProvider; +import org.eclipse.n4js.scoping.members.MemberScopingHelper; +import org.eclipse.n4js.scoping.utils.DynamicPseudoScope; +import org.eclipse.n4js.scoping.utils.LocallyKnownTypesScopingHelper; +import org.eclipse.n4js.scoping.utils.MainModuleAwareSelectableBasedScope; +import org.eclipse.n4js.scoping.utils.MergedScope; +import org.eclipse.n4js.scoping.utils.ProjectImportEnablingScope; +import org.eclipse.n4js.scoping.utils.ScopeSnapshotHelper; +import org.eclipse.n4js.scoping.utils.SourceElementExtensions; +import org.eclipse.n4js.scoping.utils.UberParentScope; +import org.eclipse.n4js.scoping.validation.ContextAwareTypeScopeValidator; +import org.eclipse.n4js.scoping.validation.ScopeInfo; +import org.eclipse.n4js.scoping.validation.VeeScopeValidator; +import org.eclipse.n4js.scoping.validation.VisibilityAwareCtorScopeValidator; +import org.eclipse.n4js.tooling.react.ReactHelper; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression; +import org.eclipse.n4js.ts.typeRefs.NamespaceLikeRef; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRefStructural; +import org.eclipse.n4js.ts.typeRefs.TypeArgument; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRefsPackage; +import org.eclipse.n4js.ts.typeRefs.TypeTypeRef; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TEnum; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.ts.types.TNamespace; +import org.eclipse.n4js.ts.types.TStructMethod; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.ts.types.TypesPackage; +import org.eclipse.n4js.ts.types.TypingStrategy; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.typesystem.N4JSTypeSystem; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper; +import org.eclipse.n4js.utils.ContainerTypesHelper; +import org.eclipse.n4js.utils.DeclMergingHelper; +import org.eclipse.n4js.utils.DeclMergingUtils; +import org.eclipse.n4js.utils.EObjectDescriptionHelper; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.ResourceType; +import org.eclipse.n4js.utils.URIUtils; +import org.eclipse.n4js.validation.JavaScriptVariantHelper; +import org.eclipse.n4js.workspace.N4JSWorkspaceConfigSnapshot; +import org.eclipse.n4js.workspace.WorkspaceAccess; +import org.eclipse.n4js.xtext.scoping.FilteringScope; +import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.resource.EObjectDescription; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.resource.IResourceDescriptions; +import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider; +import org.eclipse.xtext.scoping.IScope; +import org.eclipse.xtext.scoping.IScopeProvider; +import org.eclipse.xtext.scoping.impl.AbstractScopeProvider; +import org.eclipse.xtext.scoping.impl.IDelegatingScopeProvider; + +import com.google.common.base.Optional; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.google.inject.name.Named; + +/** + * This class contains custom scoping description. + * + * Although some methods are called according to declarative scope provider, no reflection in + * {@link #getScope(EObject,EReference)}. + * + * see : http://www.eclipse.org/Xtext/documentation/latest/xtext.html#scoping on how and when to use it + */ +@Singleton +public class N4JSScopeProvider extends AbstractScopeProvider + implements IDelegatingScopeProvider, IContentAssistScopeProvider { + + /***/ + public final static String NAMED_DELEGATE = "org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider.delegate"; + + @Inject + N4JSCache cache; + + /** + * The scope provider creating the "parent" scope, i.e. including elements from the index. At runtime, the value + * will be of type {@link N4JSImportedNamespaceAwareLocalScopeProvider}. + */ + @Inject + @Named(NAMED_DELEGATE) + IScopeProvider delegate; + + @Inject + WorkspaceAccess workspaceAccess; + + @Inject + ResourceDescriptionsProvider resourceDescriptionsProvider; + + @Inject + N4JSTypeSystem ts; + + @Inject + TypeSystemHelper tsh; + + @Inject + MemberScopingHelper memberScopingHelper; + + @Inject + LocallyKnownTypesScopingHelper locallyKnownTypesScopingHelper; + + @Inject + ImportedElementsScopingHelper importedElementsScopingHelper; + + @Inject + SourceElementExtensions sourceElementExtensions; + + @Inject + EObjectDescriptionHelper descriptionsHelper; + + @Inject + ReactHelper reactHelper; + + @Inject + JavaScriptVariantHelper jsVariantHelper; + + @Inject + MemberVisibilityChecker checker; + + @Inject + ContainerTypesHelper containerTypesHelper; + + @Inject + ExportedElementsCollector exportedElementCollector; + + @Inject + ScopeSnapshotHelper scopeSnapshotHelper; + + @Inject + DeclMergingHelper declMergingHelper; + + /***/ + protected boolean isSuppressCrossFileResolutionOfIdentifierRefs() { + return false; + } + + /** + * Delegates to {@link N4JSImportedNamespaceAwareLocalScopeProvider#getScope(EObject, EReference)}, which in turn + * delegates further to {@link N4JSGlobalScopeProvider}. + */ + protected IScope delegateGetScope(EObject context, EReference reference) { + return delegate.getScope(context, reference); + } + + @Override + public IScopeProvider getDelegate() { + return delegate; + } + + /** Dispatches to concrete scopes based on the context and reference inspection */ + @Override + public IScope getScope(EObject context, EReference reference) { + try { + // dispatch based on language variant + ResourceType resourceType = ResourceType.getResourceType(context); + switch (resourceType) { + case N4JSX: + return getN4JSXScope(context, reference); + case JSX: + return getN4JSXScope(context, reference); + default: + return getN4JSScope(context, reference); + } + + } catch (Error ex) { + if (context != null && context.eResource().getErrors().isEmpty()) { + throw ex; + } else { + // swallow exception, we got a parse error anyway + } + } + + return IScope.NULLSCOPE; + } + + /** + * Returns the N4JS scope for the given context and reference. + * + * The returned scope is not sensitive to any of the language variants of N4JS. In order to obtain a + * variant-specific scope, please use {@link N4JSScopeProvider#getScope(EObject, EReference)}. + */ + public IScope getN4JSScope(EObject context, EReference reference) { + // maybe can use scope shortcut + IScope maybeScopeShortcut = getScopeByShortcut(context, reference); + if (maybeScopeShortcut != IScope.NULLSCOPE) { + return maybeScopeShortcut; + } + + // otherwise use context: + return getScopeForContext(context, reference); + } + + /** + * shortcut to concrete scopes based on reference sniffing. Will return {@link IScope#NULLSCOPE} if no suitable + * scope found + */ + private IScope getScopeByShortcut(EObject context, EReference reference) { + if (reference == TypeRefsPackage.Literals.NAMESPACE_LIKE_REF__DECLARED_TYPE) { + if (context instanceof NamespaceLikeRef) { + NamespaceLikeRef nslr = (NamespaceLikeRef) context; + Type namespaceLikeType = nslr.getPreviousSibling() == null + ? null + : nslr.getPreviousSibling().getDeclaredType(); + + Script script = EcoreUtil2.getContainerOfType(context, Script.class); + N4NamespaceDeclaration parentNamespace = EcoreUtil2.getContainerOfType(context, + N4NamespaceDeclaration.class); + EObject parentContainer = parentNamespace == null ? script : parentNamespace; + // also check for eIsProxy in case of broken AST + EObject namespace = namespaceLikeType == null || namespaceLikeType.eIsProxy() ? parentContainer + : namespaceLikeType; + return new FilteringScope(getTypeScope(namespace, false, true), + descr -> TypesPackage.Literals.MODULE_NAMESPACE_VIRTUAL_TYPE.isSuperTypeOf(descr.getEClass()) + // only included, because classes might have a namespace merged onto them! + || TypesPackage.Literals.TCLASS.isSuperTypeOf(descr.getEClass()) + || TypesPackage.Literals.TENUM.isSuperTypeOf(descr.getEClass()) + || TypesPackage.Literals.TNAMESPACE.isSuperTypeOf(descr.getEClass())); + } + } else if (reference == TypeRefsPackage.Literals.PARAMETERIZED_TYPE_REF__DECLARED_TYPE) { + if (context instanceof ParameterizedTypeRef) { + ParameterizedTypeRef ptr = (ParameterizedTypeRef) context; + Type namespaceLikeType = null; + if (ptr.getAstNamespaceLikeRefs() != null && !ptr.getAstNamespaceLikeRefs().isEmpty()) { + namespaceLikeType = last(ptr.getAstNamespaceLikeRefs()).getDeclaredType(); + } + if (namespaceLikeType instanceof ModuleNamespaceVirtualType) { + return createScopeForNamespaceAccess((ModuleNamespaceVirtualType) namespaceLikeType, context, true, + false); + } else if (namespaceLikeType instanceof TClass) { + return createScopeForMergedNamespaces(context, namespaceLikeType, IScope.NULLSCOPE); + } else if (namespaceLikeType instanceof TEnum) { + return new DynamicPseudoScope(); + } else if (namespaceLikeType instanceof TNamespace) { + return scope_AllTopLevelElementsFromAbstractNamespace((TNamespace) namespaceLikeType, context, true, + false); + } + } + return getTypeScope(context, false, false); + } else if (reference.getEReferenceType() == N4JSPackage.Literals.LABELLED_STATEMENT) { + return scope_LabelledStatement(context); + } + return IScope.NULLSCOPE; + } + + /** dispatch to internal methods based on the context */ + private IScope getScopeForContext(EObject context, EReference reference) { + if (context instanceof ImportDeclaration) { + return scope_ImportedModule((ImportDeclaration) context); + } else if (context instanceof NamedImportSpecifier) { + return scope_ImportedElement((NamedImportSpecifier) context); + } else if (context instanceof ExportDeclaration) { + return scope_ImportedModule((ExportDeclaration) context); + } else if (context instanceof IdentifierRef) { + return scope_IdentifierRef_id((IdentifierRef) context, reference); + } else if (context instanceof BindingProperty) { + return scope_BindingProperty_property((BindingProperty) context); + } else if (context instanceof PropertyNameValuePair) { + return scope_PropertyNameValuePair_property((PropertyNameValuePair) context); + } else if (context instanceof ParameterizedPropertyAccessExpression) { + return scope_PropertyAccessExpression_property((ParameterizedPropertyAccessExpression) context); + } else if (context instanceof N4FieldAccessor) { + N4ClassifierDefinition container = EcoreUtil2.getContainerOfType((N4FieldAccessor) context, + N4ClassifierDefinition.class); + return scopeSnapshotHelper.scopeForEObjects("N4FieldAccessor", container, container.getOwnedFields()); + } + return IScope.NULLSCOPE; + } + + @Override + public IScope getScopeForContentAssist(EObject context, EReference reference) { + IScope scope = getScope(context, reference); + + if (scope == IScope.NULLSCOPE) { + // used for type references in JSDoc (see JSDocCompletionProposalComputer): + if (reference == N4JSPackage.Literals.MODULE_REF__MODULE) { + return scope_ImportedAndCurrentModule(context, reference); + } + + // the following cases are only required for content assist (i.e. scoping on broken ASTs or at unusual + // locations) otherwise use context: + + if (context instanceof Script) { + return scope_EObject_id(context, reference); + } else if (context instanceof N4TypeDeclaration) { + return scope_EObject_id(context, reference); + } else if (context instanceof VariableDeclaration) { + return scope_EObject_id(context, reference); + } else if (context instanceof Statement) { + return scope_EObject_id(context, reference); + } else if (context instanceof NewExpression) { + return scope_EObject_id(context, reference); + } else if (context instanceof ParameterizedCallExpression) { + return scope_EObject_id(context, reference); + } else if (context instanceof Argument) { + return scope_EObject_id(context, reference); + } else if (context instanceof Expression) { + return scope_EObject_id(context, reference); + } else if (context instanceof LiteralOrComputedPropertyName) { + return scope_EObject_id(context, reference); + } + } + + return scope; + } + + /** + * Returns a scope as created by {@link #getScope(EObject, EReference)} for the "from" part of an import declaration + * in the AST, but without the need for providing any AST nodes. This can be used to implement implicit imports + * without duplicating any logic from the scoping. + *

+ * There are two minor differences to the scope created by {@code #getScope()}: + *

    + *
  1. the current module, i.e. the module represented by the given resource, won"t be filtered out, and + *
  2. advanced error reporting will be disabled, i.e. {@link IScope#getSingleElement(QualifiedName)} will simply + * return null instead of returning an {@code IEObjectDescriptionWithError}. + *
+ */ + public IScope getScopeForImplicitImports(N4JSResource resource) { + return scope_ImportedModule(resource, Optional.absent()); + } + + /** + * In + * + *
+	 * continue XYZ
+	 * 
+ * + * , bind XYZ to label. Bind to ALL labels in script, although not all of them are valid. Later is to be validated + * in ASTStructureValidator and allows for better error and quick fix handling. However, most inner (and correct) + * scope is preferred (solving problems in case of duplicate names). + */ + private IScope scope_LabelledStatement(EObject context) { + IScope parent = getAllLabels((Script) EcoreUtil.getRootContainer(context)); + Set names = new HashSet<>(); + List elements = new ArrayList<>(); + EObject current = context; + while (current != null) { + if (current instanceof LabelledStatement) { + LabelledStatement ls = (LabelledStatement) current; + if (names.add(ls.getName())) { + elements.add(EObjectDescription.create(ls.getName(), current)); + } + } + current = current.eContainer(); // labeled statement must be a container + } + if (elements.isEmpty()) { + return parent; + } + IScope result = scopeSnapshotHelper.scopeFor("contextLabels", current, parent, elements); + return result; + } + + private IScope getAllLabels(Script script) { + return scopeSnapshotHelper.scopeForEObjects("allLabels", script, + toIterable(filter(script.eAllContents(), LabelledStatement.class))); + } + + /** + * E.g. in + * + *
+	 * import { e1,e2 } from "a/b/importedModule"
+	 * 
+ * + * bind "a/b/importedModule" to module with qualified name "a.b.importedModule" + */ + private IScope scope_ImportedModule(ModuleRef importOrExportDecl) { + + N4JSResource resource = (N4JSResource) importOrExportDecl.eResource(); + IScope projectImportEnabledScope = scope_ImportedModule(resource, Optional.of(importOrExportDecl)); + + // filter out clashing module name (can be main module with the same name but in different project) + return new FilteringScope(projectImportEnabledScope, descr -> (descr == null) ? false + : !descriptionsHelper.isDescriptionOfModuleWith(resource, descr, importOrExportDecl)); + } + + private IScope scope_ImportedModule(N4JSResource resource, Optional importOrExportDecl) { + + EReference reference = N4JSPackage.eINSTANCE.getModuleRef_Module(); + + IScope initialScope = scope_ImportedAndCurrentModule(resource.getScript(), reference); + + IResourceDescriptions resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(resource); + IScope delegateMainModuleAwareScope = MainModuleAwareSelectableBasedScope.createMainModuleAwareScope( + initialScope, + resourceDescriptions, reference.getEReferenceType()); + + N4JSWorkspaceConfigSnapshot ws = workspaceAccess.getWorkspaceConfig(resource); + IScope projectImportEnabledScope = ProjectImportEnablingScope.create(ws, resource, importOrExportDecl, + initialScope, delegateMainModuleAwareScope, declMergingHelper); + + return projectImportEnabledScope; + } + + /** + * Same as {@link #scope_ImportedModule(ModuleRef )}, but also includes the current module. + */ + private IScope scope_ImportedAndCurrentModule(EObject importDeclaration, EReference reference) { + return delegateGetScope(importDeclaration, reference); + } + + /** + * E.g. in + * + *
+	 * import { e1,e2 } from "importedModule"
+	 * 
+ * + * bind e1 or e2 by retrieving all (not only exported, see below!) top level elements of importedModule (variables, + * types; functions are types!). All elements enables better error handling and quick fixes, as links are not + * broken. + */ + protected IScope scope_ImportedElement(NamedImportSpecifier specifier) { + ImportDeclaration impDecl = EcoreUtil2.getContainerOfType(specifier, ImportDeclaration.class); + TModule targetModule = impDecl.getModule(); // may trigger reentrant scoping for module specifier (cf. + // #scope_ImportedModule()) + return scope_AllTopLevelElementsFromAbstractNamespace(targetModule, impDecl, true, true); + } + + /** + * E.g. in + * + *
+	 * export { e1, e2 } from "importedModule"
+	 * 
+ * + * See {@link #scope_ImportedElement(NamedImportSpecifier )}. + */ + protected IScope scope_ImportedElement(NamedExportSpecifier specifier) { + ExportDeclaration expDecl = EcoreUtil2.getContainerOfType(specifier, ExportDeclaration.class); + TModule targetModule = expDecl.getModule(); // may trigger reentrant scoping for module specifier (cf. + // #scope_ImportedModule()) + return scope_AllTopLevelElementsFromAbstractNamespace(targetModule, expDecl, true, true); + } + + /** + * Called from getScope(), binds an identifier reference. + */ + private IScope scope_IdentifierRef_id(IdentifierRef identifierRef, EReference ref) { + EObject parent = identifierRef.eContainer(); + // handle re-exports + if (parent instanceof NamedExportSpecifier) { + EObject grandParent = parent.eContainer(); + if (grandParent instanceof ExportDeclaration) { + if (((ExportDeclaration) grandParent).isReexport()) { + if (isSuppressCrossFileResolutionOfIdentifierRefs()) { + return IScope.NULLSCOPE; + } + return scope_ImportedElement((NamedExportSpecifier) parent); + } + } + } + VariableEnvironmentElement vee = ancestor(identifierRef, VariableEnvironmentElement.class); + if (vee == null) { + return IScope.NULLSCOPE; + } + ScopeInfo scope = getLexicalEnvironmentScope(vee, identifierRef, ref); + // Handle constructor visibility + if (parent instanceof NewExpression) { + return scope.addValidator( + new VisibilityAwareCtorScopeValidator(checker, containerTypesHelper, (NewExpression) parent)); + } + return scope; + } + + /** + * Only used for content assist. Modeled after method {@link #scope_IdentifierRef_id(IdentifierRef,EReference)}. + */ + private IScope scope_EObject_id(EObject obj, EReference ref) { + VariableEnvironmentElement vee = (obj instanceof VariableEnvironmentElement) + ? (VariableEnvironmentElement) obj + : ancestor(obj, VariableEnvironmentElement.class); + + if (vee == null) { + return IScope.NULLSCOPE; + } + return getLexicalEnvironmentScope(vee, obj, ref); + } + + private ScopeInfo getLexicalEnvironmentScope(VariableEnvironmentElement vee, EObject context, EReference ref) { + ensureLexicalEnvironmentScopes(context, ref); + return cache.mustGet(vee.eResource(), "scope_IdentifierRef_id", vee); + } + + private void ensureLexicalEnvironmentScopes(EObject context, EReference reference) { + Script script = (Script) EcoreUtil.getRootContainer(context); + Resource resource = script.eResource(); + Object key = N4JSCache.makeKey("scope_IdentifierRef_id", script); + boolean veeScopesBuilt = cache.contains(resource, key); // note that a script is a vee + if (!veeScopesBuilt) { + cache.get(resource, () -> buildLexicalEnvironmentScope(script, context, reference), key); + Iterator scriptIterator = script.eAllContents(); + while (scriptIterator.hasNext()) { + EObject veeObj = scriptIterator.next(); + if (veeObj instanceof VariableEnvironmentElement) { + // fill the cache + VariableEnvironmentElement vee = (VariableEnvironmentElement) veeObj; + cache.get(resource, () -> buildLexicalEnvironmentScope(vee, context, reference), + "scope_IdentifierRef_id", vee); + } + } + } + } + + /** + * Builds a lexical environment scope with the given parameters. Filters out primitive types. + */ + private ScopeInfo buildLexicalEnvironmentScope(VariableEnvironmentElement vee, EObject context, + EReference reference) { + List> scopeLists = new ArrayList<>(); + // variables declared in module + collectLexialEnvironmentsScopeLists(vee, scopeLists); + + IScope scope; + if (isSuppressCrossFileResolutionOfIdentifierRefs()) { + // Suppressing cross-file resolution is necessary for the CFG/DFG analysis + // triggered in N4JSPostProcessor#postProcessN4JSResource(...). + scope = IScope.NULLSCOPE; + } else { + Script script = (Script) EcoreUtil.getRootContainer(vee); + Resource resource = script.eResource(); + // TODO GH-2338 reconsider the following recursion guard (required for chains of re-exports in cyclic + // modules) + AtomicBoolean guard = cache.get(resource, () -> new AtomicBoolean(false), + "buildLexicalEnvironmentScope__importedValuesComputationGuard", script); + boolean alreadyInProgress = guard.getAndSet(true); + if (alreadyInProgress) { + scope = IScope.NULLSCOPE; + } else { + try { + IScope baseScope = getScriptBaseScope(script, context, reference); + // imported variables (added as second step to enable shadowing of imported elements) + scope = importedElementsScopingHelper.getImportedValues(baseScope, script); + } finally { + guard.set(false); + } + } + } + + scope = scopeSnapshotHelper.scopeForEObjects("buildLexicalEnvironmentScope", context, scope, false, + flatten(scopeLists)); + + ScopeInfo scopeInfo = new ScopeInfo(scope, scope, new VeeScopeValidator(context)); + + return scopeInfo; + } + + private IScope getScriptBaseScope(Script script, EObject context, EReference ref) { + // IDE-1065: there may be user declared globals (i.e. @@Global) + IScope globalScope = delegateGetScope(script, ref); + + if (jsVariantHelper.activateDynamicPseudoScope(context)) { // cf. sec. 13.1 + return new DynamicPseudoScope(globalScope); + } + return globalScope; + } + + private void collectLexialEnvironmentsScopeLists(VariableEnvironmentElement vee, + List> result) { + + result.add(sourceElementExtensions.collectVisibleIdentifiableElements(vee)); + + // implicit variable "arguments" must be in own outer scope in order to enable shadowing of inner variables + // named "arguments" + result.add(sourceElementExtensions.collectLocalArguments(vee)); + + VariableEnvironmentElement parent = ancestor(vee, VariableEnvironmentElement.class); + if (parent != null) { + collectLexialEnvironmentsScopeLists(parent, result); + } + } + + /** + * Creates IScope with all top level elements (variables and types, including functions), from target module or + * namespace. Provided resource is used to check visibility of module elements. Not visible elements are imported + * too, which allows better error handling and quick fixes, as links are not broken. + * + * Used for elements imported with named import and to access elements via namespace import. + * + * @param ns + * target {@link TModule} from which elements are imported + * @param context + * Receiver context {@link EObject} which is importing elements + */ + private IScope scope_AllTopLevelElementsFromAbstractNamespace(AbstractNamespace ns, EObject context, + boolean includeHollows, boolean includeValueOnlyElements) { + + return scope_AllTopLevelElementsFromAbstractNamespace(ns, context, IScope.NULLSCOPE, includeHollows, + includeValueOnlyElements); + } + + private IScope scope_AllTopLevelElementsFromAbstractNamespace(AbstractNamespace ns, EObject context, + IScope parentOrNull, + boolean includeHollows, boolean includeValueOnlyElements) { + + if (ns == null) { + return parentOrNull; + } + + Resource resource = context.eResource(); + + // TODO GH-2338 reconsider the following recursion guard (required for chains of re-exports in cyclic modules) + AtomicBoolean guard = cache.get(resource, () -> new AtomicBoolean(false), + "scope_AllTopLevelElementsFromAbstractNamespace__exportedElementsComputationGuard", context); + boolean alreadyInProgress = guard.getAndSet(true); + if (alreadyInProgress) { + return parentOrNull; + } + try { + // get regular top-level elements scope + Optional memberAccess = (context instanceof MemberAccess) + ? Optional.of((MemberAccess) context) + : Optional.absent(); + Iterable tlElems = exportedElementCollector.getExportedElements(ns, + (N4JSResource) context.eResource(), memberAccess, includeHollows, includeValueOnlyElements); + IScope topLevelElementsScope = scopeSnapshotHelper.scopeFor( + "scope_AllTopLevelElementsFromAbstractNamespace", ns, + parentOrNull != null ? parentOrNull : IScope.NULLSCOPE, false, tlElems); + return topLevelElementsScope; + } finally { + guard.set(false); + } + } + + /** + * Called from getScope(), binds a property reference. + */ + private IScope scope_BindingProperty_property(BindingProperty bindingProperty) { + return scope_DestructPattern_property(bindingProperty); + } + + /** + * Called from getScope(), binds a property reference. + */ + private IScope scope_PropertyNameValuePair_property(PropertyNameValuePair pnvPair) { + return scope_DestructPattern_property(pnvPair); + } + + private IScope scope_DestructPattern_property(EObject propertyContainer) { + TypeRef cTypeRef = null; + DestructNode destNodeTop = DestructNode.unify(DestructureUtils.getTop(propertyContainer)); + DestructNode parentDestNode = destNodeTop == null ? null + : destNodeTop.findNodeOrParentForElement(propertyContainer, true); + RuleEnvironment G = newRuleEnvironment(propertyContainer); + + if (parentDestNode != null) { + EObject parentAstElem = parentDestNode.astElement; + if (parentAstElem instanceof BindingProperty) { + BindingProperty bp = (BindingProperty) parentAstElem; + if (bp.getProperty() != null) { + cTypeRef = ts.type(G, bp.getProperty()); + } + } else if (parentAstElem instanceof BindingElement + && parentAstElem.eContainer() instanceof ArrayBindingPattern) { + DestructNode parent2DestNode = destNodeTop == null ? null + : destNodeTop.findNodeOrParentForElement(parentAstElem, true); + if (parent2DestNode != null) { + TypeRef arrayType = null; + EObject parent2AstElem = parent2DestNode.astElement; + if (parent2AstElem instanceof BindingProperty) { + BindingProperty bp = (BindingProperty) parent2AstElem; + if (bp.getProperty() != null) { + arrayType = ts.type(G, bp.getProperty()); + } + } else { + arrayType = ts.type(G, parent2DestNode.assignedElem); + } + + int idx = Arrays.asList(parent2DestNode.nestedNodes).indexOf(parentDestNode); + if (arrayType != null && arrayType.getTypeArgsWithDefaults().size() > idx + && isIterableN(G, arrayType) && arrayType.eResource() != null) { + TypeArgument typeArg = arrayType.getTypeArgsWithDefaults().get(idx); + if (typeArg instanceof TypeRef) { + cTypeRef = (TypeRef) typeArg; + } + } + } + } else if (parentDestNode.assignedElem instanceof TypeDefiningElement + && ((TypeDefiningElement) parentDestNode.assignedElem).getDefinedType() != null) { + cTypeRef = TypeUtils + .createTypeRef(((TypeDefiningElement) parentDestNode.assignedElem).getDefinedType()); + } else { + // fallback + cTypeRef = ts.type(G, parentDestNode.assignedElem); + } + if (DestructureUtils.isTopOfDestructuringForStatement(parentDestNode.astElement) && + cTypeRef != null && cTypeRef.isArrayLike()) { + tsh.addSubstitutions(G, cTypeRef); + cTypeRef = ts.substTypeVariables(G, cTypeRef.getDeclaredType().getElementType()); + } + } + + if (cTypeRef == null && propertyContainer instanceof PropertyNameValuePair + && propertyContainer.eContainer() instanceof ObjectLiteral) { + ObjectLiteral objLit = (ObjectLiteral) propertyContainer.eContainer(); + if (objLit.getDefinedType() != null) { + cTypeRef = TypeUtils.createTypeRef(objLit.getDefinedType()); + } + } + + if (cTypeRef != null && isContained(cTypeRef)) { + return new UberParentScope("scope_DestructPattern_property", + createScopeForMemberAccess(cTypeRef, propertyContainer), new DynamicPseudoScope()); + } + return new DynamicPseudoScope(); + } + + private boolean isContained(TypeRef tRef) { + if (tRef.eResource() != null) { + // type ref is contained + return true; + } + if (tRef instanceof ParameterizedTypeRefStructural) { + ParameterizedTypeRefStructural ptrs = (ParameterizedTypeRefStructural) tRef; + if (!ptrs.getAstStructuralMembers().isEmpty() || !ptrs.getGenStructuralMembers().isEmpty()) { + // type ref is not contained, hence structural members are not contained + return false; + } + } + if (tRef.getDeclaredType() != null && tRef.getDeclaredType().eResource() != null) { + // nominal type (or similar) is contained + return true; + } + return false; + } + + /* + * In
receiver.property
, binds "property". + * + */ + private IScope scope_PropertyAccessExpression_property(ParameterizedPropertyAccessExpression propertyAccess) { + Expression receiver = propertyAccess.getTarget(); + + RuleEnvironment G = newRuleEnvironment(propertyAccess); + TypeRef typeRefRaw = ts.type(G, receiver); + // take upper bound to get rid of ExistentialTypeRefs, ThisTypeRefs, etc. + // (literal types are handled in dispatch method #members() of MemberScopingHelper) + TypeRef typeRef = ts.upperBoundWithReopenAndResolveTypeVars(G, typeRefRaw); + Type declaredType = typeRef.getDeclaredType(); + if (declaredType instanceof TNamespace) { + return scope_AllTopLevelElementsFromAbstractNamespace((TNamespace) declaredType, propertyAccess, false, + true); + } + if (declaredType instanceof ModuleNamespaceVirtualType) { + return createScopeForNamespaceAccess((ModuleNamespaceVirtualType) declaredType, propertyAccess, false, + true); + } + + var result = createScopeForMemberAccess(typeRef, propertyAccess); + + // functions and classes may have namespaces merged onto them + result = handleDeclMergingForPropertyAccess(G, propertyAccess, typeRef, result); + + return result; + } + + private IScope createScopeForNamespaceAccess(ModuleNamespaceVirtualType namespace, EObject context, + boolean includeHollows, boolean includeValueOnlyElements) { + TModule module = namespace.getModule(); + + IScope result; + + if (module != null && !module.eIsProxy()) { + result = scope_AllTopLevelElementsFromAbstractNamespace(module, context, includeHollows, + includeValueOnlyElements); + } else { + // error cases + if (namespace.eIsProxy()) { + // name space does not exist -> avoid duplicate error messages + // (cf. MemberScopingHelper#members(UnknownTypeRef, MemberScopeRequest)) + result = new DynamicPseudoScope(); + } else { + // name space exists, but imported module does not -> show additional error at location of reference + result = IScope.NULLSCOPE; + } + } + if (namespace.isDeclaredDynamic() && !(result instanceof DynamicPseudoScope)) { + return new DynamicPseudoScope(result); + } + return result; + } + + private IScope createScopeForMemberAccess(TypeRef targetTypeRef, EObject context) { + boolean staticAccess = targetTypeRef instanceof TypeTypeRef; + boolean structFieldInitMode = targetTypeRef.getTypingStrategy() == TypingStrategy.STRUCTURAL_FIELD_INITIALIZER; + boolean checkVisibility = true; + IScope result = memberScopingHelper.createMemberScope(targetTypeRef, context, checkVisibility, staticAccess, + structFieldInitMode); + return result; + } + + private IScope handleDeclMergingForPropertyAccess(RuleEnvironment G, + ParameterizedPropertyAccessExpression propertyAccess, + TypeRef typeRef, IScope parent) { + + boolean staticAccess = typeRef instanceof TypeTypeRef; + + Type mergeCandidate = null; + if (staticAccess) { + Type staticType = tsh.getStaticType(G, (TypeTypeRef) typeRef, true); + if (staticType instanceof TClass) { + mergeCandidate = staticType; + } + } else { + Type declaredType = typeRef.getDeclaredType(); + if (declaredType instanceof TFunction) { + mergeCandidate = declaredType; + } + } + + if (mergeCandidate != null) { + IScope scopeNamespace = createScopeForMergedNamespaces(propertyAccess, mergeCandidate, null); + if (scopeNamespace != null) { + return new MergedScope(scopeNamespace, parent); + } + } + + return parent; + } + + /** Returns parentOrNull unchanged if no namespaces are merged onto "elem". */ + private IScope createScopeForMergedNamespaces(EObject context, Type elem, IScope parentOrNull) { + var result = parentOrNull; + if (DeclMergingUtils.mayBeMerged(elem)) { + Set mergedElems = declMergingHelper.getMergedElements((N4JSResource) context.eResource(), elem); + List mergedNamespaces = toList(filter(mergedElems, TNamespace.class)); + if (mergedNamespaces.size() > 1) { + Collections.sort(mergedNamespaces, + Comparator.comparing(ns -> ((InternalEObject) ns).eProxyURI(), new URIUtils.URIComparator())); + } + for (TNamespace mergedNS : reverseView(mergedNamespaces)) { + if (mergedNS != null) { + result = scope_AllTopLevelElementsFromAbstractNamespace(mergedNS, context, result, false, true); + } + } + } + return result; + } + + /** Returns parentOrNull unchanged if no TModules are merged onto "script". */ + private IScope createScopeForMergedTModules(Script script, boolean onlyNamespaces, IScope parentOrNull) { + if (!DeclMergingUtils.mayBeMerged(script)) { + return parentOrNull; + } + IScope result = parentOrNull; + N4JSResource resource = (N4JSResource) script.eResource(); + List mergedTModules = new ArrayList<>( + declMergingHelper.getMergedElements(resource, script.getModule())); // can be empty + if (mergedTModules.size() > 1) { + Collections.sort(mergedTModules, + Comparator.comparing(m -> ((InternalEObject) m).eProxyURI(), new URIUtils.URIComparator())); + } + for (AbstractNamespace mergedTModule : reverseView(mergedTModules)) { + if (mergedTModule != null) { + result = locallyKnownTypesScopingHelper.scopeWithLocallyDeclaredElems(mergedTModule, result, + onlyNamespaces); + } + } + return result; + } + + /** + * Is entered to initially bind "T" in + * + *
+	 * var x : T;
+	 * 
+ * + * or other parameterized type references. + */ + public IScope getTypeScope(EObject context, boolean fromStaticContext, boolean onlyNamespaces) { + IScope internal = getTypeScopeInternal(context, fromStaticContext, onlyNamespaces); + + IScope legacy = new ContextAwareTypeScope(internal, context); + ScopeInfo scopeInfo = new ScopeInfo(internal, legacy, new ContextAwareTypeScopeValidator(context)); + + return scopeInfo; + } + + private IScope getTypeScopeInternal(EObject context, boolean fromStaticContext, boolean onlyNamespaces) { + if (context instanceof Script) { + Script script = (Script) context; + return locallyKnownTypesScopingHelper.scopeWithLocallyDeclaredElems(script, () -> { + // provide any reference that expects instances of Type as target objects + IScope parent = delegateGetScope(script, + TypeRefsPackage.Literals.PARAMETERIZED_TYPE_REF__DECLARED_TYPE); + parent = createScopeForMergedTModules(script, onlyNamespaces, parent); + return parent; + }, onlyNamespaces); + + } else if (context instanceof N4NamespaceDeclaration) { + N4NamespaceDeclaration ns = (N4NamespaceDeclaration) context; + IScope parent = getTypeScopeInternal(ns.eContainer(), fromStaticContext, onlyNamespaces); + TNamespace tns = ns.getDefinedNamespace(); + if (tns != null && DeclMergingUtils.mayBeMerged(tns)) { + parent = createScopeForMergedNamespaces(tns, tns, parent); + } + return locallyKnownTypesScopingHelper.scopeWithLocallyDeclaredElems(ns, parent, onlyNamespaces); + } else if (context instanceof TNamespace) { + TNamespace tns = (TNamespace) context; + var parent = getTypeScopeInternal(tns.eContainer(), fromStaticContext, onlyNamespaces); + if (DeclMergingUtils.mayBeMerged(tns)) { + parent = createScopeForMergedNamespaces(tns, tns, parent); + } + return locallyKnownTypesScopingHelper.scopeWithLocallyDeclaredElems(tns, parent, onlyNamespaces); + } else if (context instanceof TModule) { + TModule module = (TModule) context; + Script script = (Script) module.getAstElement(); + return getTypeScopeInternal(script, fromStaticContext, onlyNamespaces); + } else if (context instanceof N4FieldDeclaration) { + N4FieldDeclaration fd = (N4FieldDeclaration) context; + boolean isStaticContext = fd.isStatic(); + // use new static access status for parent scope + return getTypeScopeInternal(fd.eContainer(), isStaticContext, onlyNamespaces); + } else if (context instanceof N4FieldAccessor) { + N4FieldAccessor fa = (N4FieldAccessor) context; + boolean isStaticContext = fa.isStatic(); + // use new static access status for parent scope + return getTypeScopeInternal(fa.eContainer(), isStaticContext, onlyNamespaces); + } else if (context instanceof TypeDefiningElement) { + TypeDefiningElement tde = (TypeDefiningElement) context; + boolean isStaticContext = tde instanceof N4MemberDeclaration && ((N4MemberDeclaration) tde).isStatic(); + // use new static access status for parent scope + IScope parent = getTypeScopeInternal(tde.eContainer(), isStaticContext, onlyNamespaces); + Type polyfilledOrOriginalType = sourceElementExtensions.getTypeOrPolyfilledType(tde); + + // use old static access status for current scope + return locallyKnownTypesScopingHelper.scopeWithTypeAndItsTypeVariables(parent, polyfilledOrOriginalType, + fromStaticContext); + } else if (context instanceof TStructMethod) { + IScope parent = getTypeScopeInternal(context.eContainer(), fromStaticContext, onlyNamespaces); + return locallyKnownTypesScopingHelper.scopeWithTypeVarsOfTStructMethod(parent, (TStructMethod) context); + } else if (context instanceof FunctionTypeExpression) { + IScope parent = getTypeScopeInternal(context.eContainer(), fromStaticContext, onlyNamespaces); + return locallyKnownTypesScopingHelper.scopeWithTypeVarsOfFunctionTypeExpression(parent, + (FunctionTypeExpression) context); + } else { + EObject container = context.eContainer(); + + // handle special areas inside a polyfill that should *not* get the usual polyfill handling implemented + // in the above case for "TypeDefiningElement": + if (container instanceof N4ClassifierDeclaration) { + N4ClassifierDeclaration cd = (N4ClassifierDeclaration) container; + if (N4JSLanguageUtils.isNonStaticPolyfill(cd) || N4JSLanguageUtils.isStaticPolyfill(cd)) { + if (cd.getTypeVars().contains(context)) { + // area #1: upper/lower bound of type parameter of polyfill, e.g. the 2nd "T" in: + // @@StaticPolyfillModule + // @StaticPolyfill export public class ToBeFilled extends ToBeFilled {} + IScope parent = getTypeScopeInternal(context.eContainer(), false, onlyNamespaces); + return locallyKnownTypesScopingHelper.scopeWithTypeAndItsTypeVariables(parent, + cd.getDefinedType(), fromStaticContext); + } else if (toList(cd.getSuperClassifierRefs()).contains(context)) { + // area #2: super type reference of polyfill, e.g. everything after "extends" in: + // @@StaticPolyfillModule + // @StaticPolyfill export public class ToBeFilled extends ToBeFilled {} + Script script = EcoreUtil2.getContainerOfType(cd, Script.class); + IScope globalScope = delegateGetScope(script, + TypeRefsPackage.Literals.PARAMETERIZED_TYPE_REF__DECLARED_TYPE); + IScope parent = locallyKnownTypesScopingHelper.scopeWithLocallyKnownTypesForPolyfillSuperRef( + script, globalScope, + cd.getDefinedType()); + return parent; + } + } + } + + return getTypeScopeInternal(container, fromStaticContext, onlyNamespaces); + } + } + + /** + * Returns ancestor of given type, or null, if no such container exists. Note that this method is slightly different + * from EcoreUtil2::getContainerOfType, as it only returns a container and not the element itself. That is, ancestor + * is not reflexive here. + */ + private T ancestor(EObject obj, Class ancestorType) { + if (obj == null) { + return null; + } + return EcoreUtil2.getContainerOfType(obj.eContainer(), ancestorType); + } + + private IScope getN4JSXScope(EObject context, EReference reference) { + IScope jsxPropertyAttributeScope = getJSXPropertyAttributeScope(context, reference); + if (jsxPropertyAttributeScope != IScope.NULLSCOPE) { + return jsxPropertyAttributeScope; + } + + IScope jsxElementScope = getJSXElementScope(context, reference); + if (jsxElementScope != IScope.NULLSCOPE) { + return jsxElementScope; + } + + // delegate to host -> N4JS scope + return getN4JSScope(context, reference); + } + + /** Returns scope for the {@link JSXPropertyAttribute} (obtained from context) or {@link IScope#NULLSCOPE} */ + private IScope getJSXPropertyAttributeScope(EObject context, EReference reference) { + if (reference == N4JSPackage.Literals.JSX_PROPERTY_ATTRIBUTE__PROPERTY) { + if (context instanceof JSXPropertyAttribute) { + JSXElement jsxElem = (JSXElement) context.eContainer(); + TypeRef propsTypeRef = reactHelper.getPropsType(jsxElem); + boolean checkVisibility = true; + boolean staticAccess = false; + boolean structFieldInitMode = false; + if (propsTypeRef != null) { + // Prevent "Cannot resolve to element" error message of unknown attributes since + // we want to issue a warning instead + TypeRef propsTypeRefUB = ts.upperBoundWithReopenAndResolveTypeVars(newRuleEnvironment(context), + propsTypeRef); + IScope memberScope = memberScopingHelper.createMemberScope(propsTypeRefUB, context, checkVisibility, + staticAccess, structFieldInitMode); + return new DynamicPseudoScope(memberScope); + } else { + IScope scope = getN4JSScope(context, reference); + return new DynamicPseudoScope(scope); + } + } + } + return IScope.NULLSCOPE; + } + + /** Returns scope for the JSXElement (obtained from context) or {@link IScope#NULLSCOPE} */ + private IScope getJSXElementScope(EObject context, EReference reference) { + + if (EcoreUtil2.getContainerOfType(context, JSXElementName.class) == null) + return IScope.NULLSCOPE; + + IScope scope = getN4JSScope(context, reference); + return new DynamicPseudoScope(scope); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/N4JSScopeProvider.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/N4JSScopeProvider.xtend deleted file mode 100644 index 42031179f7..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/N4JSScopeProvider.xtend +++ /dev/null @@ -1,977 +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.scoping - -import com.google.common.base.Optional -import com.google.inject.Inject -import com.google.inject.Singleton -import com.google.inject.name.Named -import java.util.Collections -import java.util.Comparator -import java.util.Iterator -import java.util.List -import java.util.concurrent.atomic.AtomicBoolean -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.EReference -import org.eclipse.emf.ecore.InternalEObject -import org.eclipse.emf.ecore.util.EcoreUtil -import org.eclipse.n4js.n4JS.Argument -import org.eclipse.n4js.n4JS.ArrayBindingPattern -import org.eclipse.n4js.n4JS.BindingElement -import org.eclipse.n4js.n4JS.BindingProperty -import org.eclipse.n4js.n4JS.DestructNode -import org.eclipse.n4js.n4JS.DestructureUtils -import org.eclipse.n4js.n4JS.ExportDeclaration -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.n4js.n4JS.IdentifierRef -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.JSXElement -import org.eclipse.n4js.n4JS.JSXElementName -import org.eclipse.n4js.n4JS.JSXPropertyAttribute -import org.eclipse.n4js.n4JS.LabelledStatement -import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName -import org.eclipse.n4js.n4JS.MemberAccess -import org.eclipse.n4js.n4JS.ModuleRef -import org.eclipse.n4js.n4JS.N4ClassifierDeclaration -import org.eclipse.n4js.n4JS.N4ClassifierDefinition -import org.eclipse.n4js.n4JS.N4FieldAccessor -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.N4JSPackage -import org.eclipse.n4js.n4JS.N4MemberDeclaration -import org.eclipse.n4js.n4JS.N4NamespaceDeclaration -import org.eclipse.n4js.n4JS.N4TypeDeclaration -import org.eclipse.n4js.n4JS.NamedExportSpecifier -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NewExpression -import org.eclipse.n4js.n4JS.ObjectLiteral -import org.eclipse.n4js.n4JS.ParameterizedCallExpression -import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression -import org.eclipse.n4js.n4JS.PropertyNameValuePair -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.n4JS.Statement -import org.eclipse.n4js.n4JS.TypeDefiningElement -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.n4JS.VariableEnvironmentElement -import org.eclipse.n4js.resource.N4JSCache -import org.eclipse.n4js.resource.N4JSResource -import org.eclipse.n4js.scoping.accessModifiers.ContextAwareTypeScope -import org.eclipse.n4js.scoping.accessModifiers.MemberVisibilityChecker -import org.eclipse.n4js.scoping.imports.ImportedElementsScopingHelper -import org.eclipse.n4js.scoping.imports.N4JSImportedNamespaceAwareLocalScopeProvider -import org.eclipse.n4js.scoping.members.MemberScopingHelper -import org.eclipse.n4js.scoping.utils.DynamicPseudoScope -import org.eclipse.n4js.scoping.utils.LocallyKnownTypesScopingHelper -import org.eclipse.n4js.scoping.utils.MainModuleAwareSelectableBasedScope -import org.eclipse.n4js.scoping.utils.MergedScope -import org.eclipse.n4js.scoping.utils.ProjectImportEnablingScope -import org.eclipse.n4js.scoping.utils.ScopeSnapshotHelper -import org.eclipse.n4js.scoping.utils.SourceElementExtensions -import org.eclipse.n4js.scoping.utils.UberParentScope -import org.eclipse.n4js.scoping.validation.ContextAwareTypeScopeValidator -import org.eclipse.n4js.scoping.validation.ScopeInfo -import org.eclipse.n4js.scoping.validation.VeeScopeValidator -import org.eclipse.n4js.scoping.validation.VisibilityAwareCtorScopeValidator -import org.eclipse.n4js.tooling.react.ReactHelper -import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression -import org.eclipse.n4js.ts.typeRefs.NamespaceLikeRef -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRefStructural -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRefsPackage -import org.eclipse.n4js.ts.typeRefs.TypeTypeRef -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType -import org.eclipse.n4js.ts.types.TClass -import org.eclipse.n4js.ts.types.TEnum -import org.eclipse.n4js.ts.types.TFunction -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.ts.types.TNamespace -import org.eclipse.n4js.ts.types.TStructMethod -import org.eclipse.n4js.ts.types.Type -import org.eclipse.n4js.ts.types.TypesPackage -import org.eclipse.n4js.ts.types.TypingStrategy -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.typesystem.N4JSTypeSystem -import org.eclipse.n4js.typesystem.utils.RuleEnvironment -import org.eclipse.n4js.typesystem.utils.TypeSystemHelper -import org.eclipse.n4js.utils.ContainerTypesHelper -import org.eclipse.n4js.utils.DeclMergingHelper -import org.eclipse.n4js.utils.DeclMergingUtils -import org.eclipse.n4js.utils.EObjectDescriptionHelper -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.ResourceType -import org.eclipse.n4js.utils.URIUtils -import org.eclipse.n4js.validation.JavaScriptVariantHelper -import org.eclipse.n4js.workspace.WorkspaceAccess -import org.eclipse.n4js.xtext.scoping.FilteringScope -import org.eclipse.xtext.EcoreUtil2 -import org.eclipse.xtext.resource.EObjectDescription -import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider -import org.eclipse.xtext.scoping.IScope -import org.eclipse.xtext.scoping.IScopeProvider -import org.eclipse.xtext.scoping.impl.AbstractScopeProvider -import org.eclipse.xtext.scoping.impl.IDelegatingScopeProvider - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * This class contains custom scoping description. - * - * Although some methods are called according to declarative scope provider, no reflection in - * {@link #getScope(EObject,EReference)}. - * - * see : http://www.eclipse.org/Xtext/documentation/latest/xtext.html#scoping - * on how and when to use it - */ -@Singleton -class N4JSScopeProvider extends AbstractScopeProvider implements IDelegatingScopeProvider, IContentAssistScopeProvider { - - public final static String NAMED_DELEGATE = "org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider.delegate"; - - @Inject - N4JSCache cache - - /** - * The scope provider creating the "parent" scope, i.e. including elements from the index. - * At runtime, the value will be of type {@link N4JSImportedNamespaceAwareLocalScopeProvider}. - */ - @Inject - @Named(NAMED_DELEGATE) - IScopeProvider delegate; - - @Inject WorkspaceAccess workspaceAccess; - - @Inject ResourceDescriptionsProvider resourceDescriptionsProvider; - - @Inject N4JSTypeSystem ts - - @Inject TypeSystemHelper tsh - - @Inject MemberScopingHelper memberScopingHelper - - @Inject LocallyKnownTypesScopingHelper locallyKnownTypesScopingHelper - - @Inject ImportedElementsScopingHelper importedElementsScopingHelper - - @Inject SourceElementExtensions sourceElementExtensions; - - @Inject EObjectDescriptionHelper descriptionsHelper; - - @Inject ReactHelper reactHelper; - - @Inject JavaScriptVariantHelper jsVariantHelper; - - @Inject MemberVisibilityChecker checker; - - @Inject ContainerTypesHelper containerTypesHelper; - - @Inject ExportedElementsCollector exportedElementCollector - - @Inject ScopeSnapshotHelper scopeSnapshotHelper - - @Inject DeclMergingHelper declMergingHelper; - - - protected def boolean isSuppressCrossFileResolutionOfIdentifierRefs() { - return false; - } - - /** - * Delegates to {@link N4JSImportedNamespaceAwareLocalScopeProvider#getScope(EObject, EReference)}, which in turn - * delegates further to {@link N4JSGlobalScopeProvider}. - */ - protected def IScope delegateGetScope(EObject context, EReference reference) { - return delegate.getScope(context, reference) - } - - override IScopeProvider getDelegate() { - return delegate; - } - - /** Dispatches to concrete scopes based on the context and reference inspection */ - override getScope(EObject context, EReference reference) { - try { - // dispatch based on language variant - val resourceType = ResourceType.getResourceType(context) - switch (resourceType) { - case ResourceType.N4JSX : return getN4JSXScope(context, reference) - case ResourceType.JSX : return getN4JSXScope(context, reference) - default : return getN4JSScope(context, reference) - } - - } catch (Error ex) { - if (context !== null && context.eResource.errors.empty) { - throw ex; - } else { - // swallow exception, we got a parse error anyway - } - } - - return IScope.NULLSCOPE; - } - - /** - * Returns the N4JS scope for the given context and reference. - * - * The returned scope is not sensitive to any of the language variants of N4JS. In order - * to obtain a variant-specific scope, please use {@link N4JSScopeProvider#getScope(EObject, EReference)}. - */ - public def getN4JSScope(EObject context, EReference reference) { - // maybe can use scope shortcut - val maybeScopeShortcut = getScopeByShortcut(context, reference); - if (maybeScopeShortcut !== IScope.NULLSCOPE) - return maybeScopeShortcut; - - // otherwise use context: - return getScopeForContext(context, reference) - } - - /** shortcut to concrete scopes based on reference sniffing. Will return {@link IScope#NULLSCOPE} if no suitable scope found */ - private def getScopeByShortcut(EObject context, EReference reference) { - if (reference == TypeRefsPackage.Literals.NAMESPACE_LIKE_REF__DECLARED_TYPE) { - if (context instanceof NamespaceLikeRef) { - val namespaceLikeType = context.previousSibling?.declaredType; - val script = EcoreUtil2.getContainerOfType(context, Script); - val parentNamespace = EcoreUtil2.getContainerOfType(context, N4NamespaceDeclaration); - val parentContainer = parentNamespace === null ? script : parentNamespace; - // also check for eIsProxy in case of broken AST - val namespace = namespaceLikeType === null || namespaceLikeType.eIsProxy ? parentContainer : namespaceLikeType; - return new FilteringScope(getTypeScope(namespace, false, true), [ - TypesPackage.Literals.MODULE_NAMESPACE_VIRTUAL_TYPE.isSuperTypeOf(it.getEClass) - || TypesPackage.Literals.TCLASS.isSuperTypeOf(it.getEClass) // only included, because classes might have a namespace merged onto them! - || TypesPackage.Literals.TENUM.isSuperTypeOf(it.getEClass) - || TypesPackage.Literals.TNAMESPACE.isSuperTypeOf(it.getEClass) - ]); - } - } else if (reference == TypeRefsPackage.Literals.PARAMETERIZED_TYPE_REF__DECLARED_TYPE) { - if (context instanceof ParameterizedTypeRef) { - val namespaceLikeType = context.astNamespaceLikeRefs?.last?.declaredType; - switch (namespaceLikeType) { - ModuleNamespaceVirtualType: - return createScopeForNamespaceAccess(namespaceLikeType, context, true, false) - TClass: - return createScopeForMergedNamespaces(context, namespaceLikeType, IScope.NULLSCOPE) - TEnum: - return new DynamicPseudoScope() - TNamespace: - return scope_AllTopLevelElementsFromAbstractNamespace(namespaceLikeType, context, true, false) - } - } - return getTypeScope(context, false, false); - } else if (reference.EReferenceType == N4JSPackage.Literals.LABELLED_STATEMENT) { - return scope_LabelledStatement(context); - } - return IScope.NULLSCOPE; - } - - /** dispatch to internal methods based on the context */ - private def getScopeForContext(EObject context, EReference reference) { - switch (context) { - ImportDeclaration : return scope_ImportedModule(context, reference) - NamedImportSpecifier : return scope_ImportedElement(context, reference) - ExportDeclaration : return scope_ImportedModule(context, reference) - IdentifierRef : return scope_IdentifierRef_id(context, reference) - BindingProperty : return scope_BindingProperty_property(context, reference) - PropertyNameValuePair : return scope_PropertyNameValuePair_property(context, reference) - ParameterizedPropertyAccessExpression : return scope_PropertyAccessExpression_property(context, reference) - N4FieldAccessor : { - val container = EcoreUtil2.getContainerOfType(context, N4ClassifierDefinition); - return scopeSnapshotHelper.scopeForEObjects("N4FieldAccessor", container, container.ownedFields); - } - default : return IScope.NULLSCOPE - } - } - - override getScopeForContentAssist(EObject context, EReference reference) { - val scope = getScope(context, reference); - - if (scope === IScope.NULLSCOPE) { - // used for type references in JSDoc (see JSDocCompletionProposalComputer): - if (reference == N4JSPackage.Literals.MODULE_REF__MODULE) { - return scope_ImportedAndCurrentModule(context, reference); - } - - // the following cases are only required for content assist (i.e. scoping on broken ASTs or at unusual - // locations) otherwise use context: - switch (context) { - Script: - return scope_EObject_id(context, reference) - N4TypeDeclaration: - return scope_EObject_id(context, reference) - VariableDeclaration: - return scope_EObject_id(context, reference) - Statement: - return scope_EObject_id(context, reference) - NewExpression: - return scope_EObject_id(context, reference) - ParameterizedCallExpression: - return scope_EObject_id(context, reference) - Argument: - return scope_EObject_id(context, reference) - Expression: - return scope_EObject_id(context, reference) - LiteralOrComputedPropertyName: - return scope_EObject_id(context, reference) - } - } - - return scope; - } - - /** - * Returns a scope as created by {@link #getScope(EObject, EReference)} for the 'from' part of an import declaration - * in the AST, but without the need for providing any AST nodes. This can be used to implement implicit imports - * without duplicating any logic from the scoping. - *

- * There are two minor differences to the scope created by {@code #getScope()}: - *

    - *
  1. the current module, i.e. the module represented by the given resource, won't be filtered out, and - *
  2. advanced error reporting will be disabled, i.e. {@link IScope#getSingleElement(QualifiedName)} will simply - * return null instead of returning an {@code IEObjectDescriptionWithError}. - *
- */ - public def IScope getScopeForImplicitImports(N4JSResource resource) { - return scope_ImportedModule(resource, Optional.absent()); - } - - /** - * In
continue XYZ
, bind XYZ to label. - * Bind to ALL labels in script, although not all of them are valid. Later is to be validated in ASTStructureValidator and - * allows for better error and quick fix handling. However, most inner (and correct) scope is preferred (solving problems in case of duplicate names). - */ - private def IScope scope_LabelledStatement(EObject context) { - val parent = getAllLabels(EcoreUtil.getRootContainer(context) as Script); - val names = newHashSet; - val elements = newArrayList; - var current = context; - while (current !== null) { - switch (current) { - LabelledStatement: - if (names.add(current.name)) { - elements += EObjectDescription.create(current.name, current); - } - } - current = current.eContainer; // labeled statement must be a container - } - if (elements.empty) { - return parent; - } - val result = scopeSnapshotHelper.scopeFor("contextLabels", current, parent, elements); - return result; - } - - private def IScope getAllLabels(Script script) { - return scopeSnapshotHelper.scopeForEObjects("allLabels", script, script.eAllContents.filter(LabelledStatement).toIterable); - } - - /** - * E.g. in - *
import { e1,e2 } from "a/b/importedModule"
bind "a/b/importedModule" to module with qualified name "a.b.importedModule" - */ - private def IScope scope_ImportedModule(ModuleRef importOrExportDecl, EReference reference) { - - val resource = importOrExportDecl.eResource as N4JSResource; - val projectImportEnabledScope = scope_ImportedModule(resource, Optional.of(importOrExportDecl)); - - // filter out clashing module name (can be main module with the same name but in different project) - return new FilteringScope(projectImportEnabledScope, [ - if (it === null) false else !descriptionsHelper.isDescriptionOfModuleWith(resource, it, importOrExportDecl); - ]); - } - - private def IScope scope_ImportedModule(N4JSResource resource, Optional importOrExportDecl) { - - val reference = N4JSPackage.eINSTANCE.moduleRef_Module; - - val initialScope = scope_ImportedAndCurrentModule(resource.script, reference); - - val resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(resource); - val delegateMainModuleAwareScope = MainModuleAwareSelectableBasedScope.createMainModuleAwareScope(initialScope, - resourceDescriptions, reference.EReferenceType); - - val ws = workspaceAccess.getWorkspaceConfig(resource); - val projectImportEnabledScope = ProjectImportEnablingScope.create(ws, resource, importOrExportDecl, - initialScope, delegateMainModuleAwareScope, declMergingHelper); - - return projectImportEnabledScope; - } - - /** - * Same as {@link #scope_ImportedModule(EObject,EReference)}, but also includes the current module. - */ - private def IScope scope_ImportedAndCurrentModule(EObject importDeclaration, EReference reference) { - return delegateGetScope(importDeclaration, reference); - } - - /** - * E.g. in - *
import { e1,e2 } from "importedModule"
- * bind e1 or e2 by retrieving all (not only exported, see below!) top level elements of - * importedModule (variables, types; functions are types!). All elements enables better error handling and quick fixes, as links are not broken. - */ - protected def IScope scope_ImportedElement(NamedImportSpecifier specifier, EReference reference) { - val impDecl = EcoreUtil2.getContainerOfType(specifier, ImportDeclaration); - val targetModule = impDecl.module; // may trigger reentrant scoping for module specifier (cf. #scope_ImportedModule()) - return scope_AllTopLevelElementsFromAbstractNamespace(targetModule, impDecl, true, true); - } - - /** - * E.g. in - *
export { e1, e2 } from "importedModule"
- * See {@link #scope_ImportedElement(NamedImportSpecifier, EReference)}. - */ - protected def IScope scope_ImportedElement(NamedExportSpecifier specifier, EReference reference) { - val expDecl = EcoreUtil2.getContainerOfType(specifier, ExportDeclaration); - val targetModule = expDecl.module; // may trigger reentrant scoping for module specifier (cf. #scope_ImportedModule()) - return scope_AllTopLevelElementsFromAbstractNamespace(targetModule, expDecl, true, true); - } - - /** - * Called from getScope(), binds an identifier reference. - */ - private def IScope scope_IdentifierRef_id(IdentifierRef identifierRef, EReference ref) { - val parent = identifierRef.eContainer; - // handle re-exports - if (parent instanceof NamedExportSpecifier) { - val grandParent = parent.eContainer; - if (grandParent instanceof ExportDeclaration) { - if (grandParent.isReexport()) { - if (suppressCrossFileResolutionOfIdentifierRefs) { - return IScope.NULLSCOPE; - } - return scope_ImportedElement(parent, ref); - } - } - } - val VariableEnvironmentElement vee = ancestor(identifierRef, VariableEnvironmentElement); - if (vee === null) { - return IScope.NULLSCOPE; - } - val scope = getLexicalEnvironmentScope(vee, identifierRef, ref); - // Handle constructor visibility - if (parent instanceof NewExpression) { - return scope.addValidator(new VisibilityAwareCtorScopeValidator(checker, containerTypesHelper, parent)); - } - return scope; - } - - /** - * Only used for content assist. Modeled after method {@link #scope_IdentifierRef_id(IdentifierRef,EReference)}. - */ - private def IScope scope_EObject_id(EObject obj, EReference ref) { - val VariableEnvironmentElement vee = if (obj instanceof VariableEnvironmentElement) - obj - else - ancestor(obj, VariableEnvironmentElement); - - if (vee === null) { - return IScope.NULLSCOPE; - } - return getLexicalEnvironmentScope(vee, obj, ref); - } - - private def ScopeInfo getLexicalEnvironmentScope(VariableEnvironmentElement vee, EObject context, EReference ref) { - ensureLexicalEnvironmentScopes(context, ref); - return cache.mustGet(vee.eResource, 'scope_IdentifierRef_id', vee); - } - - - private def void ensureLexicalEnvironmentScopes(EObject context, EReference reference) { - val Script script = EcoreUtil.getRootContainer(context) as Script; - val resource = script.eResource; - val key = N4JSCache.makeKey('scope_IdentifierRef_id', script); - val veeScopesBuilt = cache.contains(resource, key); // note that a script is a vee - if (!veeScopesBuilt) { - cache.get(resource, [buildLexicalEnvironmentScope(script, context, reference)], key); - val Iterator scriptIterator = script.eAllContents; - while (scriptIterator.hasNext) { - val vee = scriptIterator.next; - if (vee instanceof VariableEnvironmentElement) { - // fill the cache - cache.get(resource, [buildLexicalEnvironmentScope(vee, context, reference)], 'scope_IdentifierRef_id', vee); - } - } - } - } - - /** - * Builds a lexical environment scope with the given parameters. - * Filters out primitive types. - */ - private def ScopeInfo buildLexicalEnvironmentScope(VariableEnvironmentElement vee, EObject context, EReference reference) { - val scopeLists = newArrayList; - // variables declared in module - collectLexialEnvironmentsScopeLists(vee, scopeLists); - - var IScope scope; - if (suppressCrossFileResolutionOfIdentifierRefs) { - // Suppressing cross-file resolution is necessary for the CFG/DFG analysis - // triggered in N4JSPostProcessor#postProcessN4JSResource(...). - scope = IScope.NULLSCOPE; - } else { - val Script script = EcoreUtil.getRootContainer(vee) as Script; - val resource = script.eResource; - // TODO GH-2338 reconsider the following recursion guard (required for chains of re-exports in cyclic modules) - val guard = cache.get(resource, [new AtomicBoolean(false)], "buildLexicalEnvironmentScope__importedValuesComputationGuard", script); - val alreadyInProgress = guard.getAndSet(true); - if (alreadyInProgress) { - scope = IScope.NULLSCOPE; - } else { - try { - val IScope baseScope = getScriptBaseScope(script, context, reference); - // imported variables (added as second step to enable shadowing of imported elements) - scope = importedElementsScopingHelper.getImportedValues(baseScope, script); - } finally { - guard.set(false); - } - } - } - - - scope = scopeSnapshotHelper.scopeForEObjects("buildLexicalEnvironmentScope", context, scope, false, scopeLists.flatten); - - val scopeInfo = new ScopeInfo(scope, scope, new VeeScopeValidator(context)); - - return scopeInfo; - } - - private def IScope getScriptBaseScope(Script script, EObject context, EReference ref) { - // IDE-1065: there may be user declared globals (i.e. @@Global) - val IScope globalScope = delegateGetScope(script, ref); - - if (jsVariantHelper.activateDynamicPseudoScope(context)) { // cf. sec. 13.1 - return new DynamicPseudoScope(globalScope); - } - return globalScope; - } - - def private void collectLexialEnvironmentsScopeLists(VariableEnvironmentElement vee, - List> result) { - - result.add(sourceElementExtensions.collectVisibleIdentifiableElements(vee)); - - // implicit variable "arguments" must be in own outer scope in order to enable shadowing of inner variables named "arguments" - result.add(sourceElementExtensions.collectLocalArguments(vee)); - - val parent = ancestor(vee, VariableEnvironmentElement); - if (parent !== null) { - collectLexialEnvironmentsScopeLists(parent, result); - } - } - - /** - * Creates IScope with all top level elements (variables and types, including functions), from target module or namespace. - * Provided resource is used to check visibility of module elements. Not visible elements are imported too, which - * allows better error handling and quick fixes, as links are not broken. - * - * Used for elements imported with named import and to access elements via namespace import. - * - * @param importedModule target {@link TModule} from which elements are imported - * @param contextResource Receiver context {@link EObject} which is importing elements - */ - private def IScope scope_AllTopLevelElementsFromAbstractNamespace(AbstractNamespace ns, EObject context, - boolean includeHollows, boolean includeValueOnlyElements) { - - return scope_AllTopLevelElementsFromAbstractNamespace(ns, context, IScope.NULLSCOPE, includeHollows, includeValueOnlyElements); - } - - private def IScope scope_AllTopLevelElementsFromAbstractNamespace(AbstractNamespace ns, EObject context, IScope parentOrNull, - boolean includeHollows, boolean includeValueOnlyElements) { - - if (ns === null) { - return parentOrNull; - } - - val resource = context.eResource; - - // TODO GH-2338 reconsider the following recursion guard (required for chains of re-exports in cyclic modules) - val guard = cache.get(resource, [new AtomicBoolean(false)], "scope_AllTopLevelElementsFromAbstractNamespace__exportedElementsComputationGuard", context); - val alreadyInProgress = guard.getAndSet(true); - if (alreadyInProgress) { - return parentOrNull; - } - try { - // get regular top-level elements scope - val memberAccess = if (context instanceof MemberAccess) Optional.of(context) else Optional.absent(); - val tlElems = exportedElementCollector.getExportedElements(ns, context.eResource as N4JSResource, memberAccess, includeHollows, includeValueOnlyElements); - val topLevelElementsScope = scopeSnapshotHelper.scopeFor("scope_AllTopLevelElementsFromAbstractNamespace", ns, parentOrNull ?: IScope.NULLSCOPE, false, tlElems); - return topLevelElementsScope; - } finally { - guard.set(false); - } - } - - /** - * Called from getScope(), binds a property reference. - */ - private def IScope scope_BindingProperty_property(BindingProperty bindingProperty, EReference ref) { - return scope_DestructPattern_property(bindingProperty, ref); - } - - - /** - * Called from getScope(), binds a property reference. - */ - private def IScope scope_PropertyNameValuePair_property(PropertyNameValuePair pnvPair, EReference ref) { - return scope_DestructPattern_property(pnvPair, ref); - } - - private def IScope scope_DestructPattern_property(EObject propertyContainer, EReference ref) { - var TypeRef cTypeRef = null; - val destNodeTop = DestructNode.unify(DestructureUtils.getTop(propertyContainer)); - val parentDestNode = destNodeTop?.findNodeOrParentForElement(propertyContainer, true); - val G = newRuleEnvironment(propertyContainer); - - if (parentDestNode !== null) { - val parentAstElem = parentDestNode.astElement; - if (parentAstElem instanceof BindingProperty) { - if (parentAstElem.property !== null) { - cTypeRef = ts.type(G, parentAstElem.property); - } - } else if (parentAstElem instanceof BindingElement && parentAstElem.eContainer instanceof ArrayBindingPattern) { - val parent2DestNode = destNodeTop?.findNodeOrParentForElement(parentAstElem, true); - if (parent2DestNode !== null) { - var TypeRef arrayType = null; - val parent2AstElem = parent2DestNode.astElement; - if (parent2AstElem instanceof BindingProperty) { - if (parent2AstElem.property !== null) { - arrayType = ts.type(G, parent2AstElem.property); - } - } else { - arrayType = ts.type(G, parent2DestNode.assignedElem); - } - - val idx = parent2DestNode.nestedNodes.indexOf(parentDestNode); - if (arrayType !== null && arrayType.typeArgsWithDefaults.size > idx && G.isIterableN(arrayType) && arrayType.eResource !== null) { - val typeArg = arrayType.typeArgsWithDefaults.get(idx); - if (typeArg instanceof TypeRef) { - cTypeRef = typeArg; - } - } - } - } else if (parentDestNode.assignedElem instanceof TypeDefiningElement && (parentDestNode.assignedElem as TypeDefiningElement).definedType !== null) { - cTypeRef = TypeUtils.createTypeRef((parentDestNode.assignedElem as TypeDefiningElement).definedType); - } else { - // fallback - cTypeRef = ts.type(G, parentDestNode.assignedElem); - } - if (DestructureUtils.isTopOfDestructuringForStatement(parentDestNode.astElement) && cTypeRef.isArrayLike) { - tsh.addSubstitutions(G, cTypeRef); - cTypeRef = ts.substTypeVariables(G, cTypeRef.declaredType.elementType); - } - } - - if (cTypeRef === null && propertyContainer instanceof PropertyNameValuePair && propertyContainer.eContainer instanceof ObjectLiteral) { - val objLit = propertyContainer.eContainer as ObjectLiteral; - if (objLit.definedType !== null) { - cTypeRef = TypeUtils.createTypeRef(objLit.definedType); - } - } - - if (cTypeRef !== null && isContained(cTypeRef)) { - return new UberParentScope("scope_DestructPattern_property", createScopeForMemberAccess(cTypeRef, propertyContainer), new DynamicPseudoScope()); - } - return new DynamicPseudoScope(); - } - - private def boolean isContained(TypeRef tRef) { - if (tRef.eResource !== null) { - // type ref is contained - return true; - } - if (tRef instanceof ParameterizedTypeRefStructural) { - if (!tRef.astStructuralMembers.empty || !tRef.genStructuralMembers.empty) { - // type ref is not contained, hence structural members are not contained - return false; - } - } - if (tRef.declaredType !== null && tRef.declaredType.eResource !== null) { - // nominal type (or similar) is contained - return true; - } - return false; - } - - /* - * In
receiver.property
, binds "property". - * - */ - private def IScope scope_PropertyAccessExpression_property(ParameterizedPropertyAccessExpression propertyAccess, EReference ref) { - val Expression receiver = propertyAccess.target; - - val G = propertyAccess.newRuleEnvironment; - val TypeRef typeRefRaw = ts.type(G, receiver); - // take upper bound to get rid of ExistentialTypeRefs, ThisTypeRefs, etc. - // (literal types are handled in dispatch method #members() of MemberScopingHelper) - val TypeRef typeRef = ts.upperBoundWithReopenAndResolveTypeVars(G, typeRefRaw); - val declaredType = typeRef.declaredType; - if (declaredType instanceof TNamespace) { - return scope_AllTopLevelElementsFromAbstractNamespace(declaredType, propertyAccess, false, true); - } - if (declaredType instanceof ModuleNamespaceVirtualType) { - return createScopeForNamespaceAccess(declaredType, propertyAccess, false, true); - } - - var result = createScopeForMemberAccess(typeRef, propertyAccess); - - // functions and classes may have namespaces merged onto them - result = handleDeclMergingForPropertyAccess(G, propertyAccess, typeRef, result); - - return result; - } - - private def IScope createScopeForNamespaceAccess(ModuleNamespaceVirtualType namespace, EObject context, - boolean includeHollows, boolean includeValueOnlyElements - ) { - val module = namespace.module; - - val result = if (module !== null && !module.eIsProxy) { - scope_AllTopLevelElementsFromAbstractNamespace(module, context, includeHollows, includeValueOnlyElements); - } else { - // error cases - if (namespace.eIsProxy) { - // name space does not exist -> avoid duplicate error messages - // (cf. MemberScopingHelper#members(UnknownTypeRef, MemberScopeRequest)) - new DynamicPseudoScope() - } else { - // name space exists, but imported module does not -> show additional error at location of reference - IScope.NULLSCOPE - } - }; - if (namespace.declaredDynamic && !(result instanceof DynamicPseudoScope)) { - return new DynamicPseudoScope(result); - } - return result; - } - - private def IScope createScopeForMemberAccess(TypeRef targetTypeRef, EObject context) { - val staticAccess = targetTypeRef instanceof TypeTypeRef; - val structFieldInitMode = targetTypeRef.typingStrategy === TypingStrategy.STRUCTURAL_FIELD_INITIALIZER; - val checkVisibility = true; - val result = memberScopingHelper.createMemberScope(targetTypeRef, context, checkVisibility, staticAccess, structFieldInitMode); - return result; - } - - private def IScope handleDeclMergingForPropertyAccess(RuleEnvironment G, ParameterizedPropertyAccessExpression propertyAccess, - TypeRef typeRef, IScope parent) { - - val staticAccess = typeRef instanceof TypeTypeRef; - - var Type mergeCandidate; - if (staticAccess) { - val staticType = tsh.getStaticType(G, typeRef as TypeTypeRef, true); - if (staticType instanceof TClass) { - mergeCandidate = staticType; - } - } else { - val declaredType = typeRef.declaredType; - if (declaredType instanceof TFunction) { - mergeCandidate = declaredType; - } - } - - if (mergeCandidate !== null) { - val scopeNamespace = createScopeForMergedNamespaces(propertyAccess, mergeCandidate, null); - if (scopeNamespace !== null) { - return new MergedScope(scopeNamespace, parent); - } - } - - return parent; - } - - /** Returns parentOrNull unchanged if no namespaces are merged onto 'elem'. */ - private def IScope createScopeForMergedNamespaces(EObject context, Type elem, IScope parentOrNull) { - var result = parentOrNull; - if (DeclMergingUtils.mayBeMerged(elem)) { - val mergedElems = declMergingHelper.getMergedElements(context.eResource as N4JSResource, elem); - val mergedNamespaces = mergedElems.filter(TNamespace).toList; - if (mergedNamespaces.size > 1) { - Collections.sort(mergedNamespaces, Comparator.comparing([(it as InternalEObject).eProxyURI], new URIUtils.URIComparator())); - } - for (mergedNS : mergedNamespaces.reverseView) { - if (mergedNS !== null) { - result = scope_AllTopLevelElementsFromAbstractNamespace(mergedNS, context, result, false, true); - } - } - } - return result; - } - - /** Returns parentOrNull unchanged if no TModules are merged onto 'script'. */ - private def IScope createScopeForMergedTModules(Script script, boolean onlyNamespaces, IScope parentOrNull) { - if (!DeclMergingUtils.mayBeMerged(script)) { - return parentOrNull; - } - var result = parentOrNull; - val resource = script.eResource as N4JSResource; - val mergedTModules = declMergingHelper.getMergedElements(resource, script.module).toList(); // can be empty - if (mergedTModules.size > 1) { - Collections.sort(mergedTModules, Comparator.comparing([(it as InternalEObject).eProxyURI], new URIUtils.URIComparator())); - } - for (mergedTModule : mergedTModules.reverseView) { - if (mergedTModule !== null) { - result = locallyKnownTypesScopingHelper.scopeWithLocallyDeclaredElems(mergedTModule, result, onlyNamespaces); - } - } - return result; - } - - /** - * Is entered to initially bind "T" in
var x : T;
or other parameterized type references. - */ - def public IScope getTypeScope(EObject context, boolean fromStaticContext, boolean onlyNamespaces) { - val internal = getTypeScopeInternal(context, fromStaticContext, onlyNamespaces); - - val legacy = new ContextAwareTypeScope(internal, context); - val scopeInfo = new ScopeInfo(internal, legacy, new ContextAwareTypeScopeValidator(context)); - - return scopeInfo; - } - - def private IScope getTypeScopeInternal(EObject context, boolean fromStaticContext, boolean onlyNamespaces) { - switch context { - Script: { - return locallyKnownTypesScopingHelper.scopeWithLocallyDeclaredElems(context, [ - var parent = delegateGetScope(context, TypeRefsPackage.Literals.PARAMETERIZED_TYPE_REF__DECLARED_TYPE); // provide any reference that expects instances of Type as target objects - parent = createScopeForMergedTModules(context, onlyNamespaces, parent); - return parent; - ], onlyNamespaces); - } - N4NamespaceDeclaration: { - var parent = getTypeScopeInternal(context.eContainer, fromStaticContext, onlyNamespaces); - val ns = context.definedNamespace; - if (ns !== null && DeclMergingUtils.mayBeMerged(ns)) { - parent = createScopeForMergedNamespaces(context, ns, parent); - } - return locallyKnownTypesScopingHelper.scopeWithLocallyDeclaredElems(context, parent, onlyNamespaces); - } - TNamespace: { - var parent = getTypeScopeInternal(context.eContainer, fromStaticContext, onlyNamespaces); - if (DeclMergingUtils.mayBeMerged(context)) { - parent = createScopeForMergedNamespaces(context, context, parent); - } - return locallyKnownTypesScopingHelper.scopeWithLocallyDeclaredElems(context, parent, onlyNamespaces); - } - TModule: { - val script = context.astElement as Script; - return getTypeScopeInternal(script, fromStaticContext, onlyNamespaces); - } - N4FieldDeclaration: { - val isStaticContext = context.static; - return getTypeScopeInternal(context.eContainer, isStaticContext, onlyNamespaces); // use new static access status for parent scope - } - N4FieldAccessor: { - val isStaticContext = context.static; - return getTypeScopeInternal(context.eContainer, isStaticContext, onlyNamespaces); // use new static access status for parent scope - } - TypeDefiningElement: { - val isStaticContext = context instanceof N4MemberDeclaration && (context as N4MemberDeclaration).static; - val IScope parent = getTypeScopeInternal(context.eContainer, isStaticContext, onlyNamespaces); // use new static access status for parent scope - val polyfilledOrOriginalType = sourceElementExtensions.getTypeOrPolyfilledType(context); - - return locallyKnownTypesScopingHelper.scopeWithTypeAndItsTypeVariables(parent, polyfilledOrOriginalType, fromStaticContext); // use old static access status for current scope - } - TStructMethod: { - val parent = getTypeScopeInternal(context.eContainer, fromStaticContext, onlyNamespaces); - return locallyKnownTypesScopingHelper.scopeWithTypeVarsOfTStructMethod(parent, context); - } - FunctionTypeExpression: { - val parent = getTypeScopeInternal(context.eContainer, fromStaticContext, onlyNamespaces); - return locallyKnownTypesScopingHelper.scopeWithTypeVarsOfFunctionTypeExpression(parent, context); - } - default: { - val container = context.eContainer; - - // handle special areas inside a polyfill that should *not* get the usual polyfill handling implemented - // in the above case for "TypeDefiningElement": - if (container instanceof N4ClassifierDeclaration) { - if (N4JSLanguageUtils.isNonStaticPolyfill(container) || N4JSLanguageUtils.isStaticPolyfill(container)) { - if (container.typeVars.contains(context)) { - // area #1: upper/lower bound of type parameter of polyfill, e.g. the 2nd 'T' in: - // @@StaticPolyfillModule - // @StaticPolyfill export public class ToBeFilled extends ToBeFilled {} - val IScope parent = getTypeScopeInternal(context.eContainer, false, onlyNamespaces); - return locallyKnownTypesScopingHelper.scopeWithTypeAndItsTypeVariables(parent, container.definedType, fromStaticContext); - } else if (container.superClassifierRefs.contains(context)) { - // area #2: super type reference of polyfill, e.g. everything after 'extends' in: - // @@StaticPolyfillModule - // @StaticPolyfill export public class ToBeFilled extends ToBeFilled {} - val script = EcoreUtil2.getContainerOfType(container, Script); - val globalScope = delegateGetScope(script, TypeRefsPackage.Literals.PARAMETERIZED_TYPE_REF__DECLARED_TYPE); - val parent = locallyKnownTypesScopingHelper.scopeWithLocallyKnownTypesForPolyfillSuperRef(script, globalScope, - container.definedType); - return parent; - } - } - } - - return getTypeScopeInternal(container, fromStaticContext, onlyNamespaces); - } - } - } - - /** - * Returns ancestor of given type, or null, if no such container exists. Note that this method is slightly different from - * EcoreUtil2::getContainerOfType, as it only returns a container and not the element itself. That is, ancestor is not reflexive here. - */ - private def T ancestor(EObject obj, Class ancestorType) { - if (obj === null) return null; - return EcoreUtil2.getContainerOfType(obj.eContainer, ancestorType); - } - - private def getN4JSXScope(EObject context, EReference reference) { - val jsxPropertyAttributeScope = getJSXPropertyAttributeScope(context, reference) - if(jsxPropertyAttributeScope !== IScope.NULLSCOPE) - return jsxPropertyAttributeScope - - val jsxElementScope = getJSXElementScope(context, reference) - if(jsxElementScope !== IScope.NULLSCOPE) - return jsxElementScope - - //delegate to host -> N4JS scope - return getN4JSScope(context, reference); - } - /** Returns scope for the {@link JSXPropertyAttribute} (obtained from context) or {@link IScope#NULLSCOPE} */ - private def getJSXPropertyAttributeScope(EObject context, EReference reference) { - if (reference == N4JSPackage.Literals.JSX_PROPERTY_ATTRIBUTE__PROPERTY) { - if (context instanceof JSXPropertyAttribute) { - val jsxElem = (context.eContainer as JSXElement); - val TypeRef propsTypeRef = reactHelper.getPropsType(jsxElem); - val checkVisibility = true; - val staticAccess = false; - val structFieldInitMode = false; - if (propsTypeRef !== null) { - // Prevent "Cannot resolve to element" error message of unknown attributes since - // we want to issue a warning instead - val TypeRef propsTypeRefUB = ts.upperBoundWithReopenAndResolveTypeVars(context.newRuleEnvironment, propsTypeRef); - val memberScope = memberScopingHelper.createMemberScope(propsTypeRefUB, context, checkVisibility, - staticAccess, structFieldInitMode); - return new DynamicPseudoScope(memberScope); - } else { - val scope = getN4JSScope(context, reference); - return new DynamicPseudoScope(scope); - } - } - } - return IScope.NULLSCOPE; - } - - /** Returns scope for the JSXElement (obtained from context) or {@link IScope#NULLSCOPE} */ - private def getJSXElementScope(EObject context, EReference reference) { - - if(EcoreUtil2.getContainerOfType(context, JSXElementName) === null) - return IScope.NULLSCOPE; - - val scope = getN4JSScope(context, reference) - return new DynamicPseudoScope(scope); - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/diagnosing/N4JSScopingDiagnostician.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/diagnosing/N4JSScopingDiagnostician.java new file mode 100644 index 0000000000..98207d262e --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/diagnosing/N4JSScopingDiagnostician.java @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2017 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.scoping.diagnosing; + +import java.util.Arrays; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression; +import org.eclipse.n4js.n4JS.ParenExpression; +import org.eclipse.n4js.n4JS.RelationalExpression; +import org.eclipse.n4js.n4JS.RelationalOperator; +import org.eclipse.n4js.n4JS.SuperLiteral; +import org.eclipse.n4js.resource.ErrorAwareLinkingService; +import org.eclipse.xtext.diagnostics.DiagnosticMessage; +import org.eclipse.xtext.naming.IQualifiedNameConverter; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.nodemodel.INode; + +import com.google.inject.Inject; + +/** + * This class provides enhanced error reporting in the case that a reference can't be resolved by the N4JS scoping + * mechanisms. + * + * This class can be extended by more special cases to provide the user with more informative error messages. + */ +@SuppressWarnings("all") +public class N4JSScopingDiagnostician { + /** + * Special value to denote that no diagnostic message should be shown. + */ + public static final DiagnosticMessage NO_MESSAGE = new DiagnosticMessage( + N4JSScopingDiagnostician.class.getSimpleName() + ".NO_MESSAGE", null, null); + @Inject + private N4JSScopingConsumableMethodsDiagnosis consumableMethodsDiagnosis; + + @Inject + private N4JSScopingInstanceOfPrimitivTypeDiagnosis instanceOfPrimitiveTypeDiagnosis; + + @Inject + private ErrorAwareLinkingService linkingService; + + @Inject + private IQualifiedNameConverter qualifiedNameConverter; + + /** + * Returns a custom {@link DiagnosticMessage} for the given unresolvable reference or {@code null} if no supported + * special case is applicable and the default message should be shown. + *

+ * May return special value {@link #NO_MESSAGE} to not show an error at all for the given unresolved reference (e.g. + * to avoid duplicate error messages) . + *

+ * Note that this methods already assumes, that the given reference actually isn't resolvable. + */ + public DiagnosticMessage getMessageFor(EObject context, EReference reference, INode node) { + // use linking service here to work with the same qualified name as we do in scoping + String crossRefAsString = linkingService.getCrossRefNodeAsString(context, reference, node); + + if (null != crossRefAsString && !crossRefAsString.equals("")) { + QualifiedName qualifiedName = qualifiedNameConverter.toQualifiedName(crossRefAsString); + return diagnose(qualifiedName, context); + } + return null; + } + + /** Default dispatch method */ + private DiagnosticMessage diagnose(QualifiedName name, EObject context) { + if (context instanceof IdentifierRef) { + return diagnose(name, (IdentifierRef) context); + } else if (context instanceof ParameterizedPropertyAccessExpression) { + return diagnose(name, (ParameterizedPropertyAccessExpression) context); + } else if (context instanceof NamedImportSpecifier) { + return diagnose(name, (NamedImportSpecifier) context); + } else if (context != null) { + return null; + } + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays. asList(name, context).toString()); + } + + /** Handle {@link NamedImportSpecifier} */ + @SuppressWarnings("unused") + private DiagnosticMessage diagnose(QualifiedName name, NamedImportSpecifier context) { + // avoid duplicate error messages in case of unresolved imports + return NO_MESSAGE; + } + + /** Handle {@link ParameterizedPropertyAccessExpression} */ + private DiagnosticMessage diagnose(QualifiedName name, ParameterizedPropertyAccessExpression context) { + if (context.getTarget() instanceof SuperLiteral) { + // custom error message for referring to consumable methods via super + return consumableMethodsDiagnosis.diagnose(name, context); + } + return null; + } + + /** Handle {@link IdentifierRef}s */ + private DiagnosticMessage diagnose(QualifiedName name, IdentifierRef context) { + var container = context.eContainer(); + var containingFeature = context.eContainingFeature(); + // Skip all parenthesis-expression containers to allow + // for expressions like '((int))' + while (container instanceof ParenExpression) { + containingFeature = container.eContainmentFeature(); + container = container.eContainer(); + } + // Handle instanceof expressions + if (container instanceof RelationalExpression) { + // Check that the unresolved identifier is on the RHS of the + // operator and the operator is INSTANCEOF + RelationalExpression re = (RelationalExpression) container; + if (java.util.Objects.equals(re.getOp(), RelationalOperator.INSTANCEOF) && + java.util.Objects.equals(containingFeature, N4JSPackage.Literals.RELATIONAL_EXPRESSION__RHS)) { + return instanceOfPrimitiveTypeDiagnosis.diagnose(name, re); + } + } + return null; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/diagnosing/N4JSScopingDiagnostician.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/diagnosing/N4JSScopingDiagnostician.xtend deleted file mode 100644 index 4c6f4de05c..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/diagnosing/N4JSScopingDiagnostician.xtend +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright (c) 2017 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.scoping.diagnosing - -import com.google.inject.Inject -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.EReference -import org.eclipse.n4js.n4JS.IdentifierRef -import org.eclipse.n4js.n4JS.N4JSPackage -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression -import org.eclipse.n4js.n4JS.ParenExpression -import org.eclipse.n4js.n4JS.RelationalExpression -import org.eclipse.n4js.n4JS.RelationalOperator -import org.eclipse.n4js.n4JS.SuperLiteral -import org.eclipse.n4js.resource.ErrorAwareLinkingService -import org.eclipse.xtext.diagnostics.DiagnosticMessage -import org.eclipse.xtext.naming.IQualifiedNameConverter -import org.eclipse.xtext.naming.QualifiedName -import org.eclipse.xtext.nodemodel.INode - -/** - * This class provides enhanced error reporting in the case that - * a reference can't be resolved by the N4JS scoping mechanisms. - * - * This class can be extended by more special cases - * to provide the user with more informative error messages. - */ -class N4JSScopingDiagnostician { - - /** Special value to denote that no diagnostic message should be shown. */ - public static final DiagnosticMessage NO_MESSAGE = new DiagnosticMessage(N4JSScopingDiagnostician.simpleName + ".NO_MESSAGE", null, null); - - @Inject - N4JSScopingConsumableMethodsDiagnosis consumableMethodsDiagnosis; - - @Inject - N4JSScopingInstanceOfPrimitivTypeDiagnosis instanceOfPrimitiveTypeDiagnosis; - - @Inject - ErrorAwareLinkingService linkingService; - - @Inject - IQualifiedNameConverter qualifiedNameConverter; - - - /** - * Returns a custom {@link DiagnosticMessage} for the given unresolvable reference or {@code null} if no - * supported special case is applicable and the default message should be shown. - *

- * May return special value {@link #NO_MESSAGE} to not show an error at all for the given unresolved reference - * (e.g. to avoid duplicate error messages) . - *

- * Note that this methods already assumes, that the given reference actually isn't resolvable. - */ - public def DiagnosticMessage getMessageFor(EObject context, EReference reference, INode node) { - // use linking service here to work with the same qualified name as we do in scoping - val crossRefAsString = linkingService.getCrossRefNodeAsString(context, reference, node); - - if (null !== crossRefAsString && !crossRefAsString.equals("")) { - val qualifiedName = qualifiedNameConverter.toQualifiedName(crossRefAsString); - return diagnose(qualifiedName, context, reference); - } - return null; - } - - // Handle {@link NamedImportSpecifier} - private def dispatch DiagnosticMessage diagnose(QualifiedName name, NamedImportSpecifier context, EReference reference) { - // avoid duplicate error messages in case of unresolved imports - return NO_MESSAGE; - } - - // Handle {@link ParameterizedPropertyAccessExpressions} - private def dispatch DiagnosticMessage diagnose(QualifiedName name, ParameterizedPropertyAccessExpression context, EReference reference) { - if (context.target instanceof SuperLiteral) { - // custom error message for referring to consumable methods via super - return consumableMethodsDiagnosis.diagnose(name, context); - } - } - - // Handle {@link IdentifierRef}s - private def dispatch DiagnosticMessage diagnose(QualifiedName name, IdentifierRef context, EReference reference) { - var container = context.eContainer; - var containingFeature = context.eContainingFeature(); - // Skip all parenthesis-expression containers to allow - // for expressions like '((int))' - while (container instanceof ParenExpression) { - containingFeature = container.eContainmentFeature; - container = container.eContainer; - } - // Handle instanceof expressions - if (container instanceof RelationalExpression) { - // Check that the unresolved identifier is on the RHS of the - // operator and the operator is INSTANCEOF - if (container.op == RelationalOperator.INSTANCEOF && - containingFeature == N4JSPackage.Literals.RELATIONAL_EXPRESSION__RHS) { - return instanceOfPrimitiveTypeDiagnosis.diagnose(name, container); - } - } - } - - - // Default dispatch method - private def dispatch DiagnosticMessage diagnose(QualifiedName name, EObject context, EReference reference) { - return null; - } - -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/imports/ImportedElementsScopingHelper.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/imports/ImportedElementsScopingHelper.java new file mode 100644 index 0000000000..7ed5729253 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/imports/ImportedElementsScopingHelper.java @@ -0,0 +1,506 @@ +/** + * 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.scoping.imports; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.HashMap; +import java.util.List; +import java.util.Objects; + +import org.apache.log4j.Logger; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.n4js.n4JS.DefaultImportSpecifier; +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.resource.N4JSCache; +import org.eclipse.n4js.resource.N4JSResource; +import org.eclipse.n4js.scoping.ExportedElementsCollector; +import org.eclipse.n4js.scoping.N4JSScopeProvider; +import org.eclipse.n4js.scoping.accessModifiers.AbstractTypeVisibilityChecker; +import org.eclipse.n4js.scoping.accessModifiers.AbstractTypeVisibilityChecker.TypeVisibility; +import org.eclipse.n4js.scoping.accessModifiers.InvisibleTypeOrVariableDescription; +import org.eclipse.n4js.scoping.accessModifiers.TypeVisibilityChecker; +import org.eclipse.n4js.scoping.accessModifiers.VariableVisibilityChecker; +import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope; +import org.eclipse.n4js.scoping.builtin.GlobalObjectScope; +import org.eclipse.n4js.scoping.builtin.NoPrimitiveTypesScope; +import org.eclipse.n4js.scoping.members.MemberScope.MemberScopeFactory; +import org.eclipse.n4js.scoping.utils.LocallyKnownTypesScopingHelper; +import org.eclipse.n4js.scoping.utils.MultiImportedElementsMap; +import org.eclipse.n4js.scoping.utils.ScopeSnapshotHelper; +import org.eclipse.n4js.scoping.utils.UberParentScope; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TDynamicElement; +import org.eclipse.n4js.ts.types.TExportableElement; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.ts.types.TVariable; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.utils.ResourceType; +import org.eclipse.n4js.validation.IssueCodes; +import org.eclipse.xtext.naming.IQualifiedNameProvider; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.resource.EObjectDescription; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.resource.impl.AliasedEObjectDescription; +import org.eclipse.xtext.scoping.IScope; + +import com.google.common.base.Optional; +import com.google.common.base.Throwables; +import com.google.common.collect.Iterables; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * Helper for {@link N4JSScopeProvider N4JSScopeProvider} + * {@link N4JSScopeProvider#scope_IdentifierRef_id(IdentifierRef, EReference) .scope_IdentifierRef_id()}, also used by + * helper {@link LocallyKnownTypesScopingHelper LocallyKnownTypesScopingHelper}. + */ +@Singleton +public class ImportedElementsScopingHelper { + /** internal helper collection type */ + static public class IEODesc2ISpec extends HashMap { + /// empty + } + + private final static Logger LOGGER = Logger.getLogger(ImportedElementsScopingHelper.class); + + @Inject + N4JSCache cache; + + @Inject + private TypeVisibilityChecker typeVisibilityChecker; + + @Inject + private IQualifiedNameProvider qualifiedNameProvider; + + @Inject + private VariableVisibilityChecker variableVisibilityChecker; + + @Inject + private ImportedElementsMap.Provider elementsMapProvider; + + @Inject + private MemberScopeFactory memberScopeFactory; + + @Inject + private ScopeSnapshotHelper scopesHelper; + + @Inject + private ExportedElementsCollector exportedElementsCollector; + + /***/ + public IScope getImportedTypes(IScope parentScope, Script script) { + IScope scriptScope = cache.get(script.eResource(), () -> findImportedElements(script, parentScope, true, false), + "importedTypes", script); + return scriptScope; + } + + /***/ + public IScope getImportedValues(IScope parentScope, Script script) { + IScope scriptScope = cache.get(script.eResource(), () -> { + // filter out primitive types in next line (otherwise code like "let x = int;" would be allowed) + NoPrimitiveTypesScope noPrimitiveBuiltIns = new NoPrimitiveTypesScope( + BuiltInTypeScope.get(script.eResource().getResourceSet())); + UberParentScope uberParent = new UberParentScope("ImportedElementsScopingHelper-uberParent", + noPrimitiveBuiltIns, parentScope); + IScope globalObjectScope = getGlobalObjectProperties(uberParent, script); + IScope result = findImportedElements(script, globalObjectScope, false, true); + return result; + }, "importedValues", script); + return scriptScope; + } + + /** + * Creates a new QualifiedNamed for the given named import specifier. + * + * Determines the local name of the imported element based on the given import specifier. + */ + private QualifiedName createQualifiedNameForAlias(NamedImportSpecifier specifier) { + String importedName = (specifier.isDefaultImport()) + // we got a default import of the form: import localName from "some/module" + // -> use the string "localName" (but this is not the alias property!) + ? specifier.getImportedElementAsText() + : specifier.getAlias() != null ? specifier.getAlias() : specifier.getImportedElementAsText(); + + return QualifiedName.create(importedName); + } + + private QualifiedName createImportedQualifiedTypeName(Type type) { + return QualifiedName.create(getImportedName(type)); + } + + private String getImportedName(Type type) { + return type.getName(); + } + + private QualifiedName createImportedQualifiedTypeName(String namespace, Type type) { + return QualifiedName.create(namespace, getImportedName(type)); + } + + private IScope findImportedElements(Script script, IScope parentScope, boolean includeHollows, + boolean includeValueOnlyElements) { + N4JSResource contextResource = (N4JSResource) script.eResource(); + List imports = toList(filter(script.getScriptElements(), ImportDeclaration.class)); + + if (imports.isEmpty()) { + return parentScope; + } + + /** + * broken/invisible imported eObjects descriptions - in case of broken state of imports this can be + * {@link AmbiguousImportDescription} - in case of alias/namespace imports multiple imported elements can have + * the same qualified name + */ + MultiImportedElementsMap invalidImports = new MultiImportedElementsMap(); + /** + * valid imported eObjects descriptions (in case of broken state of imports this can be + * {@link AmbiguousImportDescription}) + */ + ImportedElementsMap validImports = elementsMapProvider.get(script); + IEODesc2ISpec originatorMap = new IEODesc2ISpec(); + + for (ImportDeclaration imp : imports) { + TModule module = imp == null ? null : imp.getModule(); + if (imp != null && module != null) { + + Iterable topLevelElements = exportedElementsCollector.getExportedElements(module, + contextResource, Optional.of(imp), includeHollows, includeValueOnlyElements); + IScope tleScope = scopesHelper.scopeFor("scope_AllTopLevelElementsFromModule", module, IScope.NULLSCOPE, + false, topLevelElements); + + for (ImportSpecifier specifier : imp.getImportSpecifiers()) { + if (specifier instanceof NamedImportSpecifier) { + processNamedImportSpecifier((NamedImportSpecifier) specifier, imp, contextResource, + originatorMap, validImports, + invalidImports, includeValueOnlyElements, tleScope); + } else if (specifier instanceof NamespaceImportSpecifier) { + processNamespaceSpecifier((NamespaceImportSpecifier) specifier, imp, script, contextResource, + originatorMap, validImports, + invalidImports, includeValueOnlyElements); + } + } + } + } + + // local broken elements are hidden by parent scope, both are hidden by valid local elements + IScope invalidLocalScope = scopesHelper.scopeFor("findImportedElements-invalidImports", script, + invalidImports.values()); + IScope localValidScope = scopesHelper.scopeFor("findImportedElements-validImports", script, parentScope, + validImports.values()); + UberParentScope importScope = new UberParentScope("findImportedElements-uberParent", localValidScope, + invalidLocalScope); + return new OriginAwareScope(script, importScope, originatorMap); + } + + private void processNamedImportSpecifier(NamedImportSpecifier specifier, ImportDeclaration imp, + Resource contextResource, IEODesc2ISpec originatorMap, + ImportedElementsMap validImports, + ImportedElementsMap invalidImports, boolean includeValueOnlyElements, IScope tleScope) { + + TExportableElement element = null; + if (specifier.isDeclaredDynamic()) { + element = findFirst(((N4JSResource) specifier.eResource()).getModule().getInternalDynamicElements(), + de -> de.getAstElement() == specifier); + } else { + String name = (specifier instanceof DefaultImportSpecifier) + ? "default" + : specifier.getImportedElementAsText(); + QualifiedName qName = QualifiedName.create(name); + + IEObjectDescription importedElem = tleScope.getSingleElement(qName); + if (importedElem != null && importedElem.getEObjectOrProxy() instanceof TExportableElement) { + element = (TExportableElement) importedElem.getEObjectOrProxy(); + } + } + + if (element != null && !element.eIsProxy()) { + + if (!includeValueOnlyElements && isValueOnlyFrom(element, imp)) { + return; + } + + QualifiedName importedQName = createQualifiedNameForAlias(specifier); + TypeVisibility typeVisibility = isVisible(contextResource, element); + if (typeVisibility.visibility) { + + addNamedImports(specifier, element, importedQName, + originatorMap, validImports); + + QualifiedName originalName = QualifiedName.create(element.getName()); + + if (specifier.getAlias() != null) { + handleAliasedAccess(element, originalName, importedQName.toString(), invalidImports); + } + } else { + handleInvisible(element, invalidImports, importedQName, typeVisibility.accessModifierSuggestion, + originatorMap, specifier); + } + } + } + + private void addNamedImports(NamedImportSpecifier specifier, TExportableElement element, QualifiedName importedName, + IEODesc2ISpec originatorMap, ImportedElementsMap validImports) { + IEObjectDescription ieod = putOrError(validImports, element, importedName, IssueCodes.IMP_AMBIGUOUS.name()); + putWithOrigin(originatorMap, ieod, specifier); + } + + private void processNamespaceSpecifier( + NamespaceImportSpecifier specifier, + ImportDeclaration imp, + Script script, + Resource contextResource, + IEODesc2ISpec originatorMap, + ImportedElementsMap validImports, + ImportedElementsMap invalidImports, + boolean includeValueOnlyElements) { + if (specifier.getAlias() == null) { + return; // if broken code, e.g. "import * as 123 as N from "some/Module"" + } + if (script.getModule() == null) { + return; // when reconciliation of TModule fails due to hash mismatch + } + + // add namespace to scope + String namespaceName = specifier.getAlias(); + QualifiedName namespaceQName = QualifiedName.create(namespaceName); + TModule sModule = script.getModule(); + + Iterable allTypes = Iterables.concat(sModule.getInternalTypes(), sModule.getExposedInternalTypes()); + + Type namespaceType = findFirst(allTypes, (interType) -> interType instanceof ModuleNamespaceVirtualType && + ((ModuleNamespaceVirtualType) interType).getModule() == imp.getModule()); + + if (namespaceType == null) { + // TODO GH-2002 remove this temporary debug logging + StringBuilder sb = new StringBuilder(); + sb.append( + "contextResource?.getURI(): " + (contextResource == null ? null : contextResource.getURI()) + "\n"); + sb.append("specifier.definedType: " + specifier.getDefinedType() + "\n"); + sb.append("imp.module: " + imp.getModule() + "\n"); + sb.append("script.module: " + sModule + "\n"); + sb.append("script.module.isPreLinkingPhase: " + sModule.isPreLinkingPhase() + "\n"); + sb.append("script.module.isReconciled: " + sModule.isReconciled() + "\n"); + sb.append("script.module.internalTypes.size: " + sModule.getInternalTypes().size() + "\n"); + sb.append("script.module.exposedInternalTypes.size: " + sModule.getExposedInternalTypes().size() + "\n"); + for (Type type : allTypes) { + if (type instanceof ModuleNamespaceVirtualType) { + sb.append("type[n].module: " + ((ModuleNamespaceVirtualType) type).getModule() + "\n"); + } + } + sb.append("\n"); + sb.append(Throwables.getStackTraceAsString(new IllegalStateException())); + LOGGER.error("namespaceType not found\n" + sb.toString()); + return; + } + IEObjectDescription ieodx = putOrError(validImports, namespaceType, namespaceQName, + IssueCodes.IMP_AMBIGUOUS.name()); + putWithOrigin(originatorMap, ieodx, specifier); + + if (includeValueOnlyElements) { + // add vars to namespace + // (this is *only* about adding some IEObjectDescriptionWithError to improve error messages) + for (TVariable importedVar : imp.getModule().getExportedVariables()) { + TypeVisibility varVisibility = variableVisibilityChecker.isVisible(contextResource, importedVar); + String varName = importedVar.getName(); + QualifiedName qn = QualifiedName.create(namespaceName, varName); + if (varVisibility.visibility) { + QualifiedName originalName = QualifiedName.create(varName); + if (!invalidImports.containsElement(originalName)) { + handleNamespacedAccess(importedVar, originalName, qn, invalidImports); + } + } + } + // add functions to namespace + // (this is *only* about adding some IEObjectDescriptionWithError to improve error messages) + for (TFunction importedFun : imp.getModule().getFunctions()) { + TypeVisibility varVisibility = typeVisibilityChecker.isVisible(contextResource, importedFun); + String varName = importedFun.getName(); + QualifiedName qn = QualifiedName.create(namespaceName, varName); + if (varVisibility.visibility) { + QualifiedName originalName = QualifiedName.create(varName); + if (!invalidImports.containsElement(originalName)) { + handleNamespacedAccess(importedFun, originalName, qn, invalidImports); + } + } + } + } + + // add types + // (this is *only* about adding some IEObjectDescriptionWithError to improve error messages) + for (Type importedType : imp.getModule().getTypes()) { + TypeVisibility typeVisibility = typeVisibilityChecker.isVisible(contextResource, importedType); + + QualifiedName qn = createImportedQualifiedTypeName(namespaceName, importedType); + if (typeVisibility.visibility) { + QualifiedName originalName = createImportedQualifiedTypeName(importedType); + if (!invalidImports.containsElement(originalName)) { + handleNamespacedAccess(importedType, originalName, qn, invalidImports); + } + } + } + } + + private void handleAliasedAccess(IdentifiableElement element, QualifiedName originalName, String importedName, + ImportedElementsMap invalidImports) { + PlainAccessOfAliasedImportDescription invalidAccess = new PlainAccessOfAliasedImportDescription( + EObjectDescription.create(originalName, element), + importedName); + invalidImports.put(originalName, invalidAccess); + // TODO IDEBUG-702 originatorMap.putWithOrigin(invalidAccess, specifier) + } + + private void handleNamespacedAccess(IdentifiableElement importedType, QualifiedName originalName, QualifiedName qn, + ImportedElementsMap invalidImports) { + PlainAccessOfNamespacedImportDescription invalidAccess = new PlainAccessOfNamespacedImportDescription( + EObjectDescription.create(originalName, importedType), qn); + invalidImports.put(originalName, invalidAccess); + // TODO IDEBUG-702 originatorMap.putWithOrigin(invalidAccess, specifier) + } + + private void handleInvisible(IdentifiableElement importedElement, ImportedElementsMap invalidImports, + QualifiedName qn, + String visibilitySuggestion, IEODesc2ISpec originatorMap, ImportSpecifier specifier) { + // TODO IDEBUG-702 val invalidAccess = new InvisibleTypeOrVariableDescription(EObjectDescription.create(qn, + // importedElement)) + IEObjectDescription invalidAccess = putOrError(invalidImports, importedElement, qn, null); + addAccessModifierSuggestion(invalidAccess, visibilitySuggestion); + putWithOrigin(originatorMap, invalidAccess, specifier); + } + + /** + * Add the description to the orginatorMap and include trace to the specifier in special Error-Aware + * IEObjectDesciptoins like AmbigousImportDescriptions. + */ + private void putWithOrigin(HashMap originiatorMap, + IEObjectDescription description, ImportSpecifier specifier) { + originiatorMap.put(description, specifier); + if (description instanceof AmbiguousImportDescription) { + AmbiguousImportDescription aid = (AmbiguousImportDescription) description; + aid.getOriginatingImports().add(specifier); + // Handling of wrapped delegatee, since this information will not be available in other cases: + ImportSpecifier firstPlaceSpec = originiatorMap.get(aid.delegate()); + // add only if not there yet: + if (firstPlaceSpec != null && !aid.getOriginatingImports().contains(firstPlaceSpec)) { + aid.getOriginatingImports().add(firstPlaceSpec); + } + } + } + + private boolean isValueOnlyFrom(IdentifiableElement element, ImportDeclaration imp) { + if (imp == null || imp.getModule() == null) { + return false; + } + if (imp.getModule().getFunctions().contains(element)) { + return true; + } + if (imp.getModule().getExportedVariables().contains(element)) { + return true; + } + return false; + } + + private AbstractTypeVisibilityChecker.TypeVisibility isVisible(Resource contextResource, + IdentifiableElement element) { + if (element instanceof TMember && ResourceType.getResourceType(element) == ResourceType.DTS) + return new AbstractTypeVisibilityChecker.TypeVisibility(true); + else if (element instanceof Type) + return typeVisibilityChecker.isVisible(contextResource, (Type) element); + else if (element instanceof TVariable) + return variableVisibilityChecker.isVisible(contextResource, (TVariable) element); + else if (element instanceof TDynamicElement) + return new AbstractTypeVisibilityChecker.TypeVisibility(true); + else + return new AbstractTypeVisibilityChecker.TypeVisibility(false); + } + + /** + * Returns {@code true} if an import of the given {@link IEObjectDescription} should be regarded as ambiguous with + * the given {@link IdentifiableElement}. + */ + @SuppressWarnings("unused") + protected boolean isAmbiguous(IEObjectDescription existing, IdentifiableElement element) { + // make sure ambiguity is only detected in case of the same imported version of a type + return true; + } + + private IEObjectDescription putOrError(ImportedElementsMap result, + IdentifiableElement element, QualifiedName importedName, String issueCode) { + // TODO IDEBUG-702 refactor InvisibleTypeOrVariableDescription / AmbiguousImportDescription relation + IEObjectDescription ret = null; + List existing = toList(result.getElements(importedName)); + + if (!existing.isEmpty() && existing.get(0) != null) { + if (issueCode != null) { + if (existing instanceof AmbiguousImportDescription) { + ((AmbiguousImportDescription) existing).getElements().add(element); + ret = (AmbiguousImportDescription) existing; + } else { + AmbiguousImportDescription error = new AmbiguousImportDescription(existing.get(0), issueCode, + element); + result.put(importedName, error); + error.getElements().add(element); + ret = error; + } + } + } else if (issueCode == null) { + ret = createDescription(importedName, element); + ret = new InvisibleTypeOrVariableDescription(ret); + result.put(importedName, ret); + } else { + ret = createDescription(importedName, element); + result.put(importedName, ret); + } + return ret; + } + + private IEObjectDescription createDescription(QualifiedName name, IdentifiableElement element) { + if (!Objects.equals(name.getLastSegment(), element.getName())) { + var qn = qualifiedNameProvider.getFullyQualifiedName(element); + if (qn == null) { + // non-directly-exported variable / function / type alias that is exported under an alias via a separate + // export declaration: + QualifiedName base = qualifiedNameProvider.getFullyQualifiedName(element.getContainingModule()); + qn = base == null ? null : base.append(element.getName()); + } + return new AliasedEObjectDescription(name, EObjectDescription.create(qn, element)); + } else { + return EObjectDescription.create(name, element); + } + } + + /** + * global object scope indirectly cached, as this method is only called by getImportedIdentifiables (which is + * cached) + */ + private IScope getGlobalObjectProperties(IScope parent, EObject context) { + TClass globalObject = GlobalObjectScope.get(context.eResource().getResourceSet()).getGlobalObject(); + return memberScopeFactory.create(parent, globalObject, context, false, false, false); + } + + private void addAccessModifierSuggestion(IEObjectDescription description, String suggestion) { + if (description instanceof InvisibleTypeOrVariableDescription) { + ((InvisibleTypeOrVariableDescription) description).setAccessModifierSuggestion(suggestion); + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/imports/ImportedElementsScopingHelper.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/imports/ImportedElementsScopingHelper.xtend deleted file mode 100644 index 9f40a9b446..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/imports/ImportedElementsScopingHelper.xtend +++ /dev/null @@ -1,471 +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.scoping.imports - -import com.google.common.base.Optional -import com.google.common.base.Throwables -import com.google.inject.Inject -import com.google.inject.Singleton -import java.util.HashMap -import org.apache.log4j.Logger -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.n4js.n4JS.DefaultImportSpecifier -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.ImportSpecifier -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.resource.N4JSCache -import org.eclipse.n4js.resource.N4JSResource -import org.eclipse.n4js.scoping.ExportedElementsCollector -import org.eclipse.n4js.scoping.N4JSScopeProvider -import org.eclipse.n4js.scoping.accessModifiers.AbstractTypeVisibilityChecker -import org.eclipse.n4js.scoping.accessModifiers.InvisibleTypeOrVariableDescription -import org.eclipse.n4js.scoping.accessModifiers.TypeVisibilityChecker -import org.eclipse.n4js.scoping.accessModifiers.VariableVisibilityChecker -import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope -import org.eclipse.n4js.scoping.builtin.GlobalObjectScope -import org.eclipse.n4js.scoping.builtin.NoPrimitiveTypesScope -import org.eclipse.n4js.scoping.members.MemberScope.MemberScopeFactory -import org.eclipse.n4js.scoping.utils.LocallyKnownTypesScopingHelper -import org.eclipse.n4js.scoping.utils.MultiImportedElementsMap -import org.eclipse.n4js.scoping.utils.ScopeSnapshotHelper -import org.eclipse.n4js.scoping.utils.UberParentScope -import org.eclipse.n4js.ts.types.IdentifiableElement -import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType -import org.eclipse.n4js.ts.types.TDynamicElement -import org.eclipse.n4js.ts.types.TExportableElement -import org.eclipse.n4js.ts.types.TMember -import org.eclipse.n4js.ts.types.TVariable -import org.eclipse.n4js.ts.types.Type -import org.eclipse.n4js.utils.ResourceType -import org.eclipse.n4js.validation.IssueCodes -import org.eclipse.xtext.naming.IQualifiedNameProvider -import org.eclipse.xtext.naming.QualifiedName -import org.eclipse.xtext.resource.EObjectDescription -import org.eclipse.xtext.resource.IEObjectDescription -import org.eclipse.xtext.resource.impl.AliasedEObjectDescription -import org.eclipse.xtext.scoping.IScope - -/** internal helper collection type */ -class IEODesc2ISpec extends HashMap {} - -/** - * Helper for {@link N4JSScopeProvider N4JSScopeProvider} - * {@link N4JSScopeProvider#scope_IdentifierRef_id(org.eclipse.n4js.n4JS.VariableEnvironmentElement) .scope_IdentifierRef_id()}, - * also used by helper {@link LocallyKnownTypesScopingHelper LocallyKnownTypesScopingHelper}. - */ -@Singleton -class ImportedElementsScopingHelper { - - private final static Logger LOGGER = Logger.getLogger(ImportedElementsScopingHelper); - - @Inject - N4JSCache cache - - @Inject - private TypeVisibilityChecker typeVisibilityChecker - - @Inject - private IQualifiedNameProvider qualifiedNameProvider - - @Inject - private VariableVisibilityChecker variableVisibilityChecker - - @Inject - private ImportedElementsMap.Provider elementsMapProvider - - @Inject - private MemberScopeFactory memberScopeFactory - - @Inject - private ScopeSnapshotHelper scopesHelper; - - @Inject - private ExportedElementsCollector exportedElementsCollector - - - - def IScope getImportedTypes(IScope parentScope, Script script) { - val IScope scriptScope = cache.get(script.eResource, [| - return findImportedElements(script, parentScope, true, false); - ], 'importedTypes', script) - return scriptScope - } - - def IScope getImportedValues(IScope parentScope, Script script) { - val IScope scriptScope = cache.get(script.eResource, [| - // filter out primitive types in next line (otherwise code like "let x = int;" would be allowed) - val noPrimitiveBuiltIns = new NoPrimitiveTypesScope(BuiltInTypeScope.get(script.eResource.resourceSet)); - val uberParent = new UberParentScope("ImportedElementsScopingHelper-uberParent", noPrimitiveBuiltIns, parentScope); - val globalObjectScope = getGlobalObjectProperties(uberParent, script); - val result = findImportedElements(script, globalObjectScope, false, true); - return result; - ], 'importedValues', script); - return scriptScope - } - - /** - * Creates a new QualifiedNamed for the given named import specifier. - * - * Determines the local name of the imported element based on the given import specifier. - */ - private def QualifiedName createQualifiedNameForAlias(NamedImportSpecifier specifier, - TExportableElement importedElement) { - val importedName = if (specifier.isDefaultImport) { - // we got a default import of the form: import localName from "some/module" - // -> use the string 'localName' (but this is not the alias property!) - specifier.importedElementAsText - } else { - specifier.alias ?: specifier.importedElementAsText - }; - return QualifiedName.create(importedName) - } - - private def QualifiedName createImportedQualifiedTypeName(Type type) { - return QualifiedName.create(getImportedName(type)); - } - - private def String getImportedName(Type type) { - return type.name; - } - - private def QualifiedName createImportedQualifiedTypeName(String namespace, Type type) { - return QualifiedName.create(namespace, getImportedName(type)); - } - - private def IScope findImportedElements(Script script, IScope parentScope, boolean includeHollows, boolean includeValueOnlyElements) { - val contextResource = script.eResource as N4JSResource; - val imports = script.scriptElements.filter(ImportDeclaration) - - if (imports.empty) return parentScope; - - /** broken/invisible imported eObjects descriptions - * - in case of broken state of imports this can be {@link AmbiguousImportDescription} - * - in case of alias/namespace imports multiple imported elements can have the same qualified name - */ - val invalidImports = new MultiImportedElementsMap(); - /** valid imported eObjects descriptions (in case of broken state of imports this can be {@link AmbiguousImportDescription})*/ - val validImports = elementsMapProvider.get(script); - val originatorMap = new IEODesc2ISpec - - for (imp : imports) { - val module = imp?.module; - if (module !== null) { - - val topLevelElements = exportedElementsCollector.getExportedElements(module, contextResource, Optional.of(imp), includeHollows, includeValueOnlyElements); - val tleScope = scopesHelper.scopeFor("scope_AllTopLevelElementsFromModule", module, IScope.NULLSCOPE, false, topLevelElements) - - for (specifier : imp.importSpecifiers) { - switch (specifier) { - NamedImportSpecifier: { - processNamedImportSpecifier(specifier, imp, contextResource, originatorMap, validImports, - invalidImports, includeValueOnlyElements, tleScope) - } - NamespaceImportSpecifier: { - processNamespaceSpecifier(specifier, imp, script, contextResource, originatorMap, validImports, - invalidImports, includeValueOnlyElements) - } - } - } - } - } - - -// local broken elements are hidden by parent scope, both are hidden by valid local elements - val invalidLocalScope = scopesHelper.scopeFor("findImportedElements-invalidImports", script, invalidImports.values) - val localValidScope = scopesHelper.scopeFor("findImportedElements-validImports", script, parentScope, validImports.values) - val importScope = new UberParentScope("findImportedElements-uberParent", localValidScope, invalidLocalScope) - return new OriginAwareScope(script, importScope, originatorMap); - } - - private def void processNamedImportSpecifier(NamedImportSpecifier specifier, ImportDeclaration imp, - Resource contextResource, IEODesc2ISpec originatorMap, - ImportedElementsMap validImports, - ImportedElementsMap invalidImports, boolean includeValueOnlyElements, IScope tleScope) { - - val element = if (specifier.declaredDynamic) { - (specifier.eResource as N4JSResource).module.internalDynamicElements.findFirst[it.astElement === specifier]; - } else { - val name = if (specifier instanceof DefaultImportSpecifier) - "default" else specifier.importedElementAsText; - val qName = QualifiedName.create(name); - - val importedElem = tleScope.getSingleElement(qName); - if (importedElem !== null && importedElem.EObjectOrProxy instanceof TExportableElement) { - importedElem.EObjectOrProxy as TExportableElement - } else { - null - } - }; - - if (element !== null && !element.eIsProxy) { - - if (!includeValueOnlyElements && element.isValueOnlyFrom(imp)) { - return; - } - - val importedQName = createQualifiedNameForAlias(specifier, element); - val typeVisibility = isVisible(contextResource, element); - if (typeVisibility.visibility) { - - addNamedImports(specifier, element, importedQName, - originatorMap, validImports); - - val originalName = QualifiedName.create(element.name) - - if (specifier.alias !== null) { - element.handleAliasedAccess(originalName, importedQName.toString, invalidImports, originatorMap, specifier) - } - } else { - element.handleInvisible(invalidImports, importedQName, typeVisibility.accessModifierSuggestion, - originatorMap, specifier) - } - } - } - - private def void addNamedImports(NamedImportSpecifier specifier, TExportableElement element, QualifiedName importedName, - IEODesc2ISpec originatorMap, ImportedElementsMap validImports) { - val ieod = validImports.putOrError(element, importedName, IssueCodes.IMP_AMBIGUOUS.name); - originatorMap.putWithOrigin(ieod, specifier) - } - - private def void processNamespaceSpecifier( - NamespaceImportSpecifier specifier, - ImportDeclaration imp, - Script script, - Resource contextResource, - IEODesc2ISpec originatorMap, - ImportedElementsMap validImports, - ImportedElementsMap invalidImports, - boolean includeValueOnlyElements - ) { - if (specifier.alias === null) { - return; // if broken code, e.g. "import * as 123 as N from 'some/Module'" - } - if (script.module === null) { - return; // when reconciliation of TModule fails due to hash mismatch - } - - // add namespace to scope - val namespaceName = specifier.alias; - val namespaceQName = QualifiedName.create(namespaceName) - val Type namespaceType = (script.module.internalTypes + script.module.exposedInternalTypes).findFirst [ interType | - interType instanceof ModuleNamespaceVirtualType && - (interType as ModuleNamespaceVirtualType).module === imp.module - ] - if (namespaceType === null) { - // TODO GH-2002 remove this temporary debug logging - val sb = new StringBuilder(); - sb.append("contextResource?.getURI(): " + contextResource?.getURI() + "\n"); - sb.append("specifier.definedType: " + specifier.definedType + "\n"); - sb.append("imp.module: " + imp.module + "\n"); - sb.append("script.module: " + script.module + "\n"); - sb.append("script.module.isPreLinkingPhase: " + script.module.isPreLinkingPhase + "\n"); - sb.append("script.module.isReconciled: " + script.module.isReconciled + "\n"); - sb.append("script.module.internalTypes.size: " + script.module.internalTypes.size + "\n"); - sb.append("script.module.exposedInternalTypes.size: " + script.module.exposedInternalTypes.size + "\n"); - for (type : (script.module.internalTypes + script.module.exposedInternalTypes)) { - if (type instanceof ModuleNamespaceVirtualType) { - sb.append("type[n].module: " + type.module + "\n"); - } - } - sb.append("\n"); - sb.append(Throwables.getStackTraceAsString(new IllegalStateException())); - LOGGER.error("namespaceType not found\n" + sb.toString); - return; - } - val ieodx = validImports.putOrError(namespaceType, namespaceQName, IssueCodes.IMP_AMBIGUOUS.name) - originatorMap.putWithOrigin(ieodx, specifier) - - if (includeValueOnlyElements) { - // add vars to namespace - // (this is *only* about adding some IEObjectDescriptionWithError to improve error messages) - for (importedVar : imp.module.exportedVariables) { - val varVisibility = variableVisibilityChecker.isVisible(contextResource, importedVar); - val varName = importedVar.name - val qn = QualifiedName.create(namespaceName, varName) - if (varVisibility.visibility) { - val originalName = QualifiedName.create(varName) - if (!invalidImports.containsElement(originalName)) { - importedVar.handleNamespacedAccess(originalName, qn, invalidImports, originatorMap, specifier) - } - } - } - // add functions to namespace - // (this is *only* about adding some IEObjectDescriptionWithError to improve error messages) - for (importedFun : imp.module.functions) { - val varVisibility = typeVisibilityChecker.isVisible(contextResource, importedFun); - val varName = importedFun.name - val qn = QualifiedName.create(namespaceName, varName) - if (varVisibility.visibility) { - val originalName = QualifiedName.create(varName) - if (!invalidImports.containsElement(originalName)) { - importedFun.handleNamespacedAccess(originalName, qn, invalidImports, originatorMap, specifier) - } - } - } - } - - // add types - // (this is *only* about adding some IEObjectDescriptionWithError to improve error messages) - for (importedType : imp.module.types) { - val typeVisibility = typeVisibilityChecker.isVisible(contextResource, importedType); - - val qn = createImportedQualifiedTypeName(namespaceName, importedType) - if (typeVisibility.visibility) { - val originalName = createImportedQualifiedTypeName(importedType) - if (!invalidImports.containsElement(originalName)) { - importedType.handleNamespacedAccess(originalName, qn, invalidImports, originatorMap, specifier) - } - } - } - } - - private def handleAliasedAccess(IdentifiableElement element, QualifiedName originalName, String importedName, - ImportedElementsMap invalidImports, IEODesc2ISpec originatorMap, ImportSpecifier specifier) { - val invalidAccess = new PlainAccessOfAliasedImportDescription(EObjectDescription.create(originalName, element), - importedName) - invalidImports.put(originalName, invalidAccess) - // TODO IDEBUG-702 originatorMap.putWithOrigin(invalidAccess, specifier) - } - - private def handleNamespacedAccess(IdentifiableElement importedType, QualifiedName originalName, QualifiedName qn, - ImportedElementsMap invalidImports, IEODesc2ISpec originatorMap, ImportSpecifier specifier) { - val invalidAccess = new PlainAccessOfNamespacedImportDescription( - EObjectDescription.create(originalName, importedType), qn) - invalidImports.put(originalName, invalidAccess) - // TODO IDEBUG-702 originatorMap.putWithOrigin(invalidAccess, specifier) - } - - private def handleInvisible(IdentifiableElement importedElement, ImportedElementsMap invalidImports, QualifiedName qn, - String visibilitySuggestion, IEODesc2ISpec originatorMap, ImportSpecifier specifier) { - // TODO IDEBUG-702 val invalidAccess = new InvisibleTypeOrVariableDescription(EObjectDescription.create(qn, importedElement)) - val invalidAccess = invalidImports.putOrError(importedElement, qn, null) - invalidAccess.addAccessModifierSuggestion(visibilitySuggestion) - originatorMap.putWithOrigin(invalidAccess, specifier) - } - - /** Add the description to the orginatorMap and include trace to the specifier in special Error-Aware IEObjectDesciptoins - * like AmbigousImportDescriptions. - */ - private def putWithOrigin(HashMap originiatorMap, - IEObjectDescription description, ImportSpecifier specifier) { - originiatorMap.put(description, specifier); - switch (description) { - AmbiguousImportDescription: { - description.originatingImports.add(specifier); - // Handling of wrapped delegatee, since this information will not be available in other cases: - val firstPlaceSpec = originiatorMap.get(description.delegate) - // add only if not there yet: - if (firstPlaceSpec !== null && ! description.originatingImports.contains(firstPlaceSpec)) { - description.originatingImports.add(firstPlaceSpec) - } - } - } - } - - def private boolean isValueOnlyFrom(IdentifiableElement element, ImportDeclaration imp) { - if (imp?.module === null) { - return false; - } - if (imp.module.functions.contains(element)) { - return true - } - if (imp.module.exportedVariables.contains(element)) { - return true - } - return false; - } - - private def AbstractTypeVisibilityChecker.TypeVisibility isVisible(Resource contextResource, - IdentifiableElement element) { - if (element instanceof TMember && ResourceType.getResourceType(element) == ResourceType.DTS) - new AbstractTypeVisibilityChecker.TypeVisibility(true) - else if (element instanceof Type) - typeVisibilityChecker.isVisible(contextResource, element) - else if (element instanceof TVariable) - variableVisibilityChecker.isVisible(contextResource, element) - else if (element instanceof TDynamicElement) - return new AbstractTypeVisibilityChecker.TypeVisibility(true) - else - return new AbstractTypeVisibilityChecker.TypeVisibility(false); - } - - /** - * Returns {@code true} if an import of the given {@link IEObjectDescription} should be - * regarded as ambiguous with the given {@link IdentifiableElement}. - */ - protected def boolean isAmbiguous(IEObjectDescription existing, IdentifiableElement element) { - // make sure ambiguity is only detected in case of the same imported version of a type - return true; - } - - private def IEObjectDescription putOrError(ImportedElementsMap result, - IdentifiableElement element, QualifiedName importedName, String issueCode) { - // TODO IDEBUG-702 refactor InvisibleTypeOrVariableDescription / AmbiguousImportDescription relation - var IEObjectDescription ret = null; - val existing = result.getElements(importedName) - - if (!existing.empty && existing.get(0) !== null) { - if (issueCode !== null) { - switch existing { - AmbiguousImportDescription: { - existing.elements += element; - ret = existing - } - default: { - val error = new AmbiguousImportDescription(existing.head, issueCode, element) - result.put(importedName, error) - error.elements += element; - ret = error - } - } - } - } else if (issueCode === null) { - ret = createDescription(importedName, element) - ret = new InvisibleTypeOrVariableDescription(ret) - result.put(importedName, ret) - } else { - ret = createDescription(importedName, element) - result.put(importedName, ret) - } - return ret; - } - - private def IEObjectDescription createDescription(QualifiedName name, IdentifiableElement element) { - if (name.lastSegment != element.name) { - var qn = qualifiedNameProvider.getFullyQualifiedName(element); - if (qn === null) { - // non-directly-exported variable / function / type alias that is exported under an alias via a separate export declaration: - qn = qualifiedNameProvider.getFullyQualifiedName(element.containingModule)?.append(element.name); - } - return new AliasedEObjectDescription(name, EObjectDescription.create(qn, element)) - } else { - return EObjectDescription.create(name, element) - } - } - - /** - * global object scope indirectly cached, as this method is only called by getImportedIdentifiables (which is cached) - */ - private def IScope getGlobalObjectProperties(IScope parent, EObject context) { - val globalObject = GlobalObjectScope.get(context.eResource.resourceSet).getGlobalObject - memberScopeFactory.create(parent, globalObject, context, false, false, false) - } - - private def void addAccessModifierSuggestion(IEObjectDescription description, String suggestion) { - if (description instanceof InvisibleTypeOrVariableDescription) { - description.accessModifierSuggestion = suggestion; - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/imports/SingleImportedElementsMap.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/imports/SingleImportedElementsMap.java new file mode 100644 index 0000000000..6186d61f2b --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/imports/SingleImportedElementsMap.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2017 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.scoping.imports; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.resource.IEObjectDescription; + +/** + * HashMap-based implementation of {@link ImportedElementsMap}. + * + * This map can only hold a single {@link IEObjectDescription} per qualified name. + */ +public class SingleImportedElementsMap implements ImportedElementsMap { + private final HashMap elementsMap = new HashMap<>(); + + @Override + public boolean containsElement(QualifiedName name) { + return this.elementsMap.containsKey(name); + } + + @Override + public Iterable getElements(QualifiedName name) { + IEObjectDescription result = this.elementsMap.get(name); + if (null != result) { + return List.of(result); + } else { + return Collections.emptyList(); + } + } + + @Override + public void put(QualifiedName name, IEObjectDescription element) { + this.elementsMap.put(name, element); + } + + @Override + public Iterable values() { + return this.elementsMap.values(); + } + +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/imports/SingleImportedElementsMap.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/imports/SingleImportedElementsMap.xtend deleted file mode 100644 index 3a8bb8c8a9..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/imports/SingleImportedElementsMap.xtend +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2017 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.scoping.imports - -import java.util.HashMap -import org.eclipse.xtext.naming.QualifiedName -import org.eclipse.xtext.resource.IEObjectDescription - -/** - * HashMap-based implementation of {@link ImportedElementsMap}. - * - * This map can only hold a single {@link IEObjectDescription} per qualified name. - */ -public class SingleImportedElementsMap implements ImportedElementsMap { - private HashMap elementsMap; - - new() { - this.elementsMap = newHashMap(); - } - - override containsElement(QualifiedName name) { - return this.elementsMap.containsKey(name); - } - - override getElements(QualifiedName name) { - val result = this.elementsMap.get(name); - if (null !== result) { - return #[result]; - } else { - return emptyList; - } - } - - override put(QualifiedName name, IEObjectDescription element) { - this.elementsMap.put(name, element); - } - - override values() { - this.elementsMap.values; - } - -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/members/MemberScopingHelper.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/members/MemberScopingHelper.java new file mode 100644 index 0000000000..1a475cd725 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/members/MemberScopingHelper.java @@ -0,0 +1,565 @@ +/** + * 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.scoping.members; + +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.booleanTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.newRuleEnvironment; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.numberTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.stringTypeRef; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filterNull; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.n4js.n4JS.MemberAccess; +import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression; +import org.eclipse.n4js.scoping.accessModifiers.MemberVisibilityChecker; +import org.eclipse.n4js.scoping.accessModifiers.StaticWriteAccessFilterScope; +import org.eclipse.n4js.scoping.accessModifiers.VisibilityAwareMemberScope; +import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope; +import org.eclipse.n4js.scoping.builtin.N4Scheme; +import org.eclipse.n4js.scoping.utils.CompositeScope; +import org.eclipse.n4js.scoping.utils.DynamicPseudoScope; +import org.eclipse.n4js.scoping.utils.UberParentScope; +import org.eclipse.n4js.ts.typeRefs.BooleanLiteralTypeRef; +import org.eclipse.n4js.ts.typeRefs.EnumLiteralTypeRef; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExprOrRef; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeRef; +import org.eclipse.n4js.ts.typeRefs.IntersectionTypeExpression; +import org.eclipse.n4js.ts.typeRefs.LiteralTypeRef; +import org.eclipse.n4js.ts.typeRefs.NumericLiteralTypeRef; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRefStructural; +import org.eclipse.n4js.ts.typeRefs.StringLiteralTypeRef; +import org.eclipse.n4js.ts.typeRefs.ThisTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeTypeRef; +import org.eclipse.n4js.ts.typeRefs.UnionTypeExpression; +import org.eclipse.n4js.ts.typeRefs.UnknownTypeRef; +import org.eclipse.n4js.ts.types.ContainerType; +import org.eclipse.n4js.ts.types.PrimitiveType; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TEnum; +import org.eclipse.n4js.ts.types.TStructuralType; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.ts.types.TypingStrategy; +import org.eclipse.n4js.ts.types.UndefinedType; +import org.eclipse.n4js.typesystem.N4JSTypeSystem; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper; +import org.eclipse.n4js.utils.EcoreUtilN4; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind; +import org.eclipse.n4js.validation.JavaScriptVariantHelper; +import org.eclipse.n4js.xtext.scoping.FilterWithErrorMarkerScope; +import org.eclipse.n4js.xtext.scoping.IEObjectDescriptionWithError; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.scoping.IScope; +import org.eclipse.xtext.scoping.Scopes; + +import com.google.inject.Inject; + +/** + */ +public class MemberScopingHelper { + @Inject + N4JSTypeSystem ts; + @Inject + TypeSystemHelper tsh; + @Inject + MemberScope.MemberScopeFactory memberScopeFactory; + @Inject + private MemberVisibilityChecker memberVisibilityChecker; + @Inject + private JavaScriptVariantHelper jsVariantHelper; + + /** + * Create a new member scope that filters using the given criteria (visibility, static access). Members retrieved + * via the scope returned by this method are guaranteed to be contained in a resource. + *

+ * When choosing static scope, the {@code context} is inspected to determine read/write access but only if it's a + * {@link ParameterizedPropertyAccessExpression} or a {@code IndexedAccessExpression}. + * + * @param receiverTypeRef + * TypeRef for the value whose scope is of interest. + * @param context + * AST node used for (a) obtaining context resource, (b) visibility checking, and (c) caching composed + * members. + * @param checkVisibility + * if true, the member scope will be wrapped in a {@link VisibilityAwareMemberScope}. + * @param staticAccess + * true: only static members are relevant; false: only non-static ones. + * @param structFieldInitMode + * see {@link AbstractMemberScope#structFieldInitMode}. + */ + public IScope createMemberScope(TypeRef receiverTypeRef, EObject context, + boolean checkVisibility, boolean staticAccess, boolean structFieldInitMode) { + + MemberScopeRequest request = new MemberScopeRequest(receiverTypeRef, context, true, checkVisibility, + staticAccess, + structFieldInitMode, receiverTypeRef.isDynamic()); + return decoratedMemberScopeFor(receiverTypeRef, request); + } + + /** + * Same as {@link #createMemberScope(TypeRef, EObject, boolean, boolean, boolean)}, but the returned scope + * DOES NOT guarantee that members will be contained in a resource (in particular, composed members + * will not be contained in a resource). In turn, this method does not require a context of type + * {@link MemberAccess}. + *

+ * This method can be used if members are only used temporarily for a purpose that does not require proper + * containment of the member, e.g. retrieving the type of a field for validation purposes. There are two reasons for + * using this method: + *

    + *
  1. client code is unable to provide a context of type {@link MemberAccess}, + *
  2. client code wants to invoke {@link IScope#getAllElements()} or similar methods on the returned scope and + * wants to avoid unnecessary caching of all those members. + *
+ */ + public IScope createMemberScopeAllowingNonContainedMembers(TypeRef receiverTypeRef, EObject context, + boolean checkVisibility, boolean staticAccess, boolean structFieldInitMode) { + + MemberScopeRequest request = new MemberScopeRequest(receiverTypeRef, context, false, checkVisibility, + staticAccess, + structFieldInitMode, receiverTypeRef.isDynamic()); + return decoratedMemberScopeFor(receiverTypeRef, request); + } + + /** + * Creates member scope via #members and decorates it via #decorate. + */ + private IScope decoratedMemberScopeFor(TypeRef typeRef, MemberScopeRequest memberScopeRequest) { + if (typeRef == null) { + return IScope.NULLSCOPE; + } + var result = members(typeRef, memberScopeRequest); + return result; + } + + /** + * Called only be members functions to decorate returned scope. + */ + private IScope decorate(IScope scope, MemberScopeRequest memberScopeRequest, TypeRef receiverTypeRef) { + if (scope == IScope.NULLSCOPE) { + return scope; + } + IScope decoratedScope = scope; + if (memberScopeRequest.checkVisibility && + !FilterWithErrorMarkerScope.isDecoratedWithFilter(scope, VisibilityAwareMemberScope.class)) { + decoratedScope = new VisibilityAwareMemberScope(decoratedScope, memberVisibilityChecker, receiverTypeRef, + memberScopeRequest.context); + } + if (memberScopeRequest.staticAccess && + !FilterWithErrorMarkerScope.isDecoratedWithFilter(scope, StaticWriteAccessFilterScope.class)) { + decoratedScope = new StaticWriteAccessFilterScope(decoratedScope, memberScopeRequest.context); + } + if (memberScopeRequest.checkVisibility && + !FilterWithErrorMarkerScope.isDecoratedWithFilter(scope, TypingStrategyAwareMemberScope.class)) { + decoratedScope = new TypingStrategyAwareMemberScope(decoratedScope, receiverTypeRef, + memberScopeRequest.context); + } + return decoratedScope; + } + + /** + * For the member given by (name, staticAccess) return the erroneous descriptions from the given scope. + */ + public Iterable getErrorsForMember(IScope scope, String memberName) { + Iterable descriptions = scope.getElements(QualifiedName.create(memberName)); + Iterable errorsOrNulls = map(descriptions, + d -> IEObjectDescriptionWithError.getDescriptionWithError(d)); + return filterNull(errorsOrNulls); + } + + @SuppressWarnings("unused") + private IScope members(UnknownTypeRef type, MemberScopeRequest request) { + return new DynamicPseudoScope(); + } + + @SuppressWarnings("unused") + private IScope members(LiteralTypeRef ltr, MemberScopeRequest request) { + throw new UnsupportedOperationException("missing method for " + ltr.eClass().getName()); + } + + @SuppressWarnings("unused") + private IScope members(BooleanLiteralTypeRef ltr, MemberScopeRequest request) { + RuleEnvironment G = newRuleEnvironment(request.context); + return members(booleanTypeRef(G), request); + } + + @SuppressWarnings("unused") + private IScope members(NumericLiteralTypeRef ltr, MemberScopeRequest request) { + RuleEnvironment G = newRuleEnvironment(request.context); + return members(numberTypeRef(G), request); // no need to distinguish between number and int + } + + @SuppressWarnings("unused") + private IScope members(StringLiteralTypeRef ltr, MemberScopeRequest request) { + RuleEnvironment G = newRuleEnvironment(request.context); + return members(stringTypeRef(G), request); + } + + private IScope members(EnumLiteralTypeRef ltr, MemberScopeRequest request) { + RuleEnvironment G = newRuleEnvironment(request.context); + return members(N4JSLanguageUtils.getLiteralTypeBase(G, ltr), request); + } + + private IScope members(ParameterizedTypeRef ptr, MemberScopeRequest request) { + IScope result = membersOfType(ptr.getDeclaredType(), request); + if (ptr.isDynamic() && !(result instanceof DynamicPseudoScope)) { + return new DynamicPseudoScope(decorate(result, request, ptr)); + } + return decorate(result, request, ptr); + } + + private IScope members(ParameterizedTypeRefStructural ptrs, MemberScopeRequest request) { + IScope result = membersOfType(ptrs.getDeclaredType(), request); + if (ptrs.isDynamic() && !(result instanceof DynamicPseudoScope)) { + return new DynamicPseudoScope(decorate(result, request, ptrs)); + } + if (ptrs.getStructuralMembers().isEmpty()) { + return decorate(result, request, ptrs); + } + IScope memberScopeRaw; + if (ptrs.getStructuralType() != null) { + memberScopeRaw = memberScopeFactory.create(result, ptrs.getStructuralType(), request.context, + request.staticAccess, + request.structFieldInitMode, request.isDynamicType); + } else { + // note: these are not the members of the defined type + // however, we only scope locally, so that doesn't matter + memberScopeRaw = memberScopeFactory.create(result, ptrs.getStructuralMembers(), request.context, + request.staticAccess, + request.structFieldInitMode, request.isDynamicType); + } + + return decorate(memberScopeRaw, request, ptrs); + } + + /** + * Note: N4JSScopeProvider already taking the upper bound before using this class (thus resolving ThisTypeRefs + * beforehand), so we will never enter this method from there; still provided to support uses from other code. + */ + private IScope members(ThisTypeRef thisTypeRef, MemberScopeRequest request) { + // taking the upper bound to "resolve" the ThisTypeRef: + // this[C] --> C (ParameterizedTypeRef) + // ~~this[C] with { number prop; } --> ~~C with { number prop; } (ParameterizedTypeRefStructural) + TypeRef ub = ts.upperBoundWithReopen(newRuleEnvironment(request.context), thisTypeRef); + + if (ub != null) { // ThisTypeRef was resolved + return members(ub, request); + } + + // probably an unbound ThisTypeRef or some other error (reported elsewhere) + return IScope.NULLSCOPE; + } + + private IScope members(TypeTypeRef ttr, MemberScopeRequest request) { + MemberScopeRequest staticRequest = request.enforceStatic(); + RuleEnvironment G = newRuleEnvironment(request.context); + Type ctrStaticType = tsh.getStaticType(G, ttr, true); + IScope staticMembers = membersOfType(ctrStaticType, staticRequest); // staticAccess is always true in this case + if (ctrStaticType instanceof TEnum) { + // enums have their literals as static members + staticMembers = decorate(Scopes.scopeFor(((TEnum) ctrStaticType).getLiterals(), staticMembers), request, + ttr); + } + if (ttr.isDynamic() && !(staticMembers instanceof DynamicPseudoScope)) { + staticMembers = new DynamicPseudoScope(decorate(staticMembers, staticRequest, ttr)); + } + // in addition, we need instance members of either Function (in case of constructor{T}) or Object (for type{T}) + // except for @NumberBased/@StringBased enums: + if (ctrStaticType instanceof TEnum && N4JSLanguageUtils.getEnumKind((TEnum) ctrStaticType) != EnumKind.Normal) { + return decorate(staticMembers, staticRequest, ttr); + } + MemberScopeRequest instanceRequest = request.enforceInstance(); + BuiltInTypeScope builtInScope = BuiltInTypeScope.get(getResourceSet(ttr, request.context)); + TClassifier functionType = (ttr.isConstructorRef()) ? builtInScope.getFunctionType() + : builtInScope.getObjectType(); + IScope ftypeScope = membersOfType(functionType, instanceRequest); + IScope result = CompositeScope.create( + // order matters (shadowing!) + decorate(staticMembers, staticRequest, ttr), + decorate(ftypeScope, instanceRequest, ttr)); + return result; + } + + private IScope members(UnionTypeExpression uniontypeexp, MemberScopeRequest request) { + if (jsVariantHelper.activateDynamicPseudoScope(request.context)) { // cf. sec. 13.1 + return new DynamicPseudoScope(); + } + + RuleEnvironment G = RuleEnvironmentExtensions.newRuleEnvironment(request.context); + IScope anyPlusScope = null; + List subScopes = new ArrayList<>(); + for (TypeRef elementTypeRef : uniontypeexp.getTypeRefs()) { + boolean structFieldInitMode = elementTypeRef + .getTypingStrategy() == TypingStrategy.STRUCTURAL_FIELD_INITIALIZER; + IScope scope = members(elementTypeRef, request.setStructFieldInitMode(structFieldInitMode)); + if (RuleEnvironmentExtensions.isAnyDynamic(G, elementTypeRef)) { + anyPlusScope = scope; + } else { + subScopes.add(scope); + } + } + + // only create union scope if really necessary, remember this optimization in test, since union{A} tests scope + // of A only! + IScope subScope = null; + if (subScopes.size() == 1) { + subScope = subScopes.get(0); + } else if (subScopes.size() > 1) { + subScope = new UnionMemberScope(uniontypeexp, request, subScopes, ts); + } + + if (anyPlusScope == null && subScope == null) { + return IScope.NULLSCOPE; + } + if (anyPlusScope == null) { + return subScope; + } + if (subScope == null) { + return anyPlusScope; + } + return new UberParentScope("", subScope, anyPlusScope); + } + + private IScope members(IntersectionTypeExpression intersectiontypeexp, MemberScopeRequest request) { + if (intersectiontypeexp.getTypeRefs().isEmpty()) { + return IScope.NULLSCOPE; + } + + RuleEnvironment G = RuleEnvironmentExtensions.newRuleEnvironment(request.context); + IScope anyPlusScope = null; + List subScopes = new ArrayList<>(); + for (TypeRef elementTypeRef : intersectiontypeexp.getTypeRefs()) { + boolean structFieldInitMode = elementTypeRef + .getTypingStrategy() == TypingStrategy.STRUCTURAL_FIELD_INITIALIZER; + IScope scope = members(elementTypeRef, request.setStructFieldInitMode(structFieldInitMode)); + if (RuleEnvironmentExtensions.isAnyDynamic(G, elementTypeRef)) { + anyPlusScope = scope; + } else { + subScopes.add(scope); + } + } + + // only create union scope if really necessary, remember this optimization in test, since union{A} tests scope + // of A only! + IScope subScope = null; + if (subScopes.size() == 1) { + subScope = subScopes.get(0); + } else if (subScopes.size() > 1) { + subScope = new IntersectionMemberScope(intersectiontypeexp, request, subScopes, ts); + } + + if (anyPlusScope == null && subScope == null) { + return IScope.NULLSCOPE; + } + if (subScope != null) { + return subScope; + } + return anyPlusScope; + } + + private IScope members(FunctionTypeRef ftExpr, MemberScopeRequest request) { + return membersOfFunctionTypeRef(ftExpr, request); + } + + private IScope members(FunctionTypeExpression ftExpr, MemberScopeRequest request) { + return membersOfFunctionTypeRef(ftExpr, request); + } + + /** + * delegated from two methods above, to avoid catch-all of ParameterizedTypeRef for FuntionTypeRefs while + * dispatching + */ + private IScope membersOfFunctionTypeRef(FunctionTypeExprOrRef ftExpr, MemberScopeRequest request) { + BuiltInTypeScope builtInTypeScope = BuiltInTypeScope.get(getResourceSet(ftExpr, request.context)); + TClass fType = builtInTypeScope.getFunctionType(); + IScope ret = membersOfType(fType, request); + return decorate(ret, request, ftExpr); + } + + @SuppressWarnings("unused") + private IScope membersOfType(UndefinedType type, MemberScopeRequest request) { + if (jsVariantHelper.activateDynamicPseudoScope(request.context)) { // cf. sec. 13.1 + return new DynamicPseudoScope(); + } + + return IScope.NULLSCOPE; + } + + @SuppressWarnings("unused") + private IScope membersOfType(Void type, MemberScopeRequest request) { + return new DynamicPseudoScope(); + } + + /** + * Primitive types have no members, but they can be auto-boxed to their corresponding object type which then, + * transparently to the user, provide members. + */ + private IScope membersOfType(PrimitiveType prim, MemberScopeRequest request) { + TClassifier boxedType = prim.getAutoboxedType(); + return (boxedType != null) ? membersOfType(boxedType, request) : IScope.NULLSCOPE; + } + + /** + * Creates member scope with parent containing members of implicit super types. + */ + private IScope membersOfType(ContainerType type, MemberScopeRequest request) { + IScope parentScope = (jsVariantHelper.activateDynamicPseudoScope(request.context)) + // cf. sec. 13.1 + ? new DynamicPseudoScope() + : IScope.NULLSCOPE; + + if (!request.staticAccess && type instanceof TClass && N4Scheme.isFromResourceWithN4Scheme(type)) { + // classifiers defined in builtin_js.n4jsd and builtin_n4.n4jsd are allowed to extend primitive + // types, and the following is required to support auto-boxing in such a case: + Type rootSuperType = getRootSuperType((TClass) type); + if (rootSuperType instanceof PrimitiveType) { + TClassifier boxedType = ((PrimitiveType) rootSuperType).getAutoboxedType(); + if (boxedType != null) { + parentScope = memberScopeFactory.create(parentScope, boxedType, request.context, + request.staticAccess, request.structFieldInitMode, request.isDynamicType); + } + } + } + + return memberScopeFactory.create(parentScope, type, request.context, request.staticAccess, + request.structFieldInitMode, request.isDynamicType); + } + + /** + * Returns a scope of the literals, that is members such as name or value. That is, the instance members of an + * enumeration. The static members are made available in {@link #members(EnumLiteralTypeRef, MemberScopeRequest)} + */ + private IScope membersOfType(TEnum enumeration, MemberScopeRequest request) { + BuiltInTypeScope builtInTypeScope = BuiltInTypeScope.get(getResourceSet(enumeration, request.context)); + // IDE-1221 select built-in type depending on whether this enumeration is tagged number-/string-based + EnumKind enumKind = N4JSLanguageUtils.getEnumKind(enumeration); + TClass specificEnumType = null; + switch (enumKind) { + case Normal: + specificEnumType = builtInTypeScope.getN4EnumType(); + break; + case NumberBased: + specificEnumType = builtInTypeScope.getN4NumberBasedEnumType(); + break; + case StringBased: + specificEnumType = builtInTypeScope.getN4StringBasedEnumType(); + break; + } + return membersOfType(specificEnumType, request); + } + + private IScope membersOfType(TStructuralType structType, MemberScopeRequest request) { + if (structType.getOwnedMembers().isEmpty()) { + return IScope.NULLSCOPE; + } + return memberScopeFactory.create(structType, request.context, request.staticAccess, request.structFieldInitMode, + request.isDynamicType); + } + + private ResourceSet getResourceSet(EObject type, EObject context) { + var result = EcoreUtilN4.getResourceSet(type, context); + if (result == null) { + throw new IllegalStateException("type or context must be contained in a ResourceSet"); + } + return result; + } + + private Type getRootSuperType(TClass type) { + Type curr = type; + Type next; + do { + next = null; + if (curr instanceof TClass) { + TClass tc = (TClass) curr; + next = tc.getSuperClassRef() == null ? null : tc.getSuperClassRef().getDeclaredType(); + } + if (next != null) { + curr = next; + } + } while (next != null); + return curr; + } + + private IScope members(TypeRef ftExpr, MemberScopeRequest request) { + if (ftExpr instanceof FunctionTypeRef) { + return members((FunctionTypeRef) ftExpr, request); + } else if (ftExpr instanceof ParameterizedTypeRefStructural) { + return members((ParameterizedTypeRefStructural) ftExpr, request); + } else if (ftExpr instanceof FunctionTypeExpression) { + return members((FunctionTypeExpression) ftExpr, request); + } else if (ftExpr instanceof IntersectionTypeExpression) { + return members((IntersectionTypeExpression) ftExpr, request); + } else if (ftExpr instanceof ParameterizedTypeRef) { + return members((ParameterizedTypeRef) ftExpr, request); + } else if (ftExpr instanceof ThisTypeRef) { + return members((ThisTypeRef) ftExpr, request); + } else if (ftExpr instanceof TypeTypeRef) { + return members((TypeTypeRef) ftExpr, request); + } else if (ftExpr instanceof UnionTypeExpression) { + return members((UnionTypeExpression) ftExpr, request); + } else if (ftExpr instanceof BooleanLiteralTypeRef) { + return members((BooleanLiteralTypeRef) ftExpr, request); + } else if (ftExpr instanceof EnumLiteralTypeRef) { + return members((EnumLiteralTypeRef) ftExpr, request); + } else if (ftExpr instanceof NumericLiteralTypeRef) { + return members((NumericLiteralTypeRef) ftExpr, request); + } else if (ftExpr instanceof StringLiteralTypeRef) { + return members((StringLiteralTypeRef) ftExpr, request); + } else if (ftExpr instanceof LiteralTypeRef) { + return members((LiteralTypeRef) ftExpr, request); + } else if (ftExpr instanceof UnknownTypeRef) { + return members((UnknownTypeRef) ftExpr, request); + } else if (ftExpr != null) { + return IScope.NULLSCOPE; + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(ftExpr, request).toString()); + } + } + + private IScope membersOfType(Type structType, MemberScopeRequest request) { + if (structType instanceof TStructuralType) { + return membersOfType((TStructuralType) structType, request); + } else if (structType instanceof ContainerType) { + return membersOfType((ContainerType) structType, request); + } else if (structType instanceof PrimitiveType) { + return membersOfType((PrimitiveType) structType, request); + } else if (structType instanceof UndefinedType) { + return membersOfType((UndefinedType) structType, request); + } else if (structType instanceof TEnum) { + return membersOfType((TEnum) structType, request); + } else if (structType != null) { + // TODO member computation should be extracted + if (structType.eIsProxy()) { + return new DynamicPseudoScope(); + } + if (jsVariantHelper.activateDynamicPseudoScope(request.context)) { // cf. sec. 13.1 + return new DynamicPseudoScope(); + } + + return IScope.NULLSCOPE; + } else { + return membersOfType((Void) null, request); + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/members/MemberScopingHelper.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/members/MemberScopingHelper.xtend deleted file mode 100644 index 6c3a67d388..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/members/MemberScopingHelper.xtend +++ /dev/null @@ -1,469 +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.scoping.members - -import com.google.inject.Inject -import java.util.ArrayList -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.resource.ResourceSet -import org.eclipse.n4js.n4JS.MemberAccess -import org.eclipse.n4js.n4JS.ParameterizedPropertyAccessExpression -import org.eclipse.n4js.scoping.accessModifiers.MemberVisibilityChecker -import org.eclipse.n4js.scoping.accessModifiers.StaticWriteAccessFilterScope -import org.eclipse.n4js.scoping.accessModifiers.VisibilityAwareMemberScope -import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope -import org.eclipse.n4js.scoping.builtin.N4Scheme -import org.eclipse.n4js.scoping.utils.CompositeScope -import org.eclipse.n4js.scoping.utils.DynamicPseudoScope -import org.eclipse.n4js.scoping.utils.UberParentScope -import org.eclipse.n4js.ts.typeRefs.BooleanLiteralTypeRef -import org.eclipse.n4js.ts.typeRefs.EnumLiteralTypeRef -import org.eclipse.n4js.ts.typeRefs.FunctionTypeExprOrRef -import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression -import org.eclipse.n4js.ts.typeRefs.FunctionTypeRef -import org.eclipse.n4js.ts.typeRefs.IntersectionTypeExpression -import org.eclipse.n4js.ts.typeRefs.LiteralTypeRef -import org.eclipse.n4js.ts.typeRefs.NumericLiteralTypeRef -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRefStructural -import org.eclipse.n4js.ts.typeRefs.StringLiteralTypeRef -import org.eclipse.n4js.ts.typeRefs.ThisTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.typeRefs.TypeTypeRef -import org.eclipse.n4js.ts.typeRefs.UnionTypeExpression -import org.eclipse.n4js.ts.typeRefs.UnknownTypeRef -import org.eclipse.n4js.ts.types.ContainerType -import org.eclipse.n4js.ts.types.PrimitiveType -import org.eclipse.n4js.ts.types.TClass -import org.eclipse.n4js.ts.types.TEnum -import org.eclipse.n4js.ts.types.TStructuralType -import org.eclipse.n4js.ts.types.Type -import org.eclipse.n4js.ts.types.TypingStrategy -import org.eclipse.n4js.ts.types.UndefinedType -import org.eclipse.n4js.typesystem.N4JSTypeSystem -import org.eclipse.n4js.typesystem.utils.TypeSystemHelper -import org.eclipse.n4js.utils.EcoreUtilN4 -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind -import org.eclipse.n4js.validation.JavaScriptVariantHelper -import org.eclipse.n4js.xtext.scoping.FilterWithErrorMarkerScope -import org.eclipse.n4js.xtext.scoping.IEObjectDescriptionWithError -import org.eclipse.xtext.naming.QualifiedName -import org.eclipse.xtext.scoping.IScope -import org.eclipse.xtext.scoping.Scopes - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* -import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions - -/** - */ -class MemberScopingHelper { - @Inject N4JSTypeSystem ts; - @Inject TypeSystemHelper tsh; - @Inject MemberScope.MemberScopeFactory memberScopeFactory - @Inject private MemberVisibilityChecker memberVisibilityChecker - @Inject private JavaScriptVariantHelper jsVariantHelper; - - /** - * Create a new member scope that filters using the given criteria (visibility, static access). Members retrieved - * via the scope returned by this method are guaranteed to be contained in a resource. - *

- * When choosing static scope, the {@code context} is inspected to determine read/write access - * but only if it's a {@link ParameterizedPropertyAccessExpression} or a {@code IndexedAccessExpression}. - * - * @param receiverTypeRef - * TypeRef for the value whose scope is of interest. - * @param context - * AST node used for (a) obtaining context resource, (b) visibility checking, and - * (c) caching composed members. - * @param checkVisibility - * if true, the member scope will be wrapped in a {@link VisibilityAwareMemberScope}; if - * false, method {@link getPropertyTypeForNode(IScope,String)} will never return - * {@link #INVISIBLE_MEMBER}. - * @param staticAccess - * true: only static members are relevant; false: only non-static ones. - * @param structFieldInitMode - * see {@link AbstractMemberScope#structFieldInitMode}. - */ - public def IScope createMemberScope(TypeRef receiverTypeRef, EObject context, - boolean checkVisibility, boolean staticAccess, boolean structFieldInitMode) { - - val request = new MemberScopeRequest(receiverTypeRef, context, true, checkVisibility, staticAccess, - structFieldInitMode, receiverTypeRef.isDynamic); - return decoratedMemberScopeFor(receiverTypeRef, request); - } - - /** - * Same as {@link #createMemberScope(TypeRef, MemberAccess, boolean, boolean)}, but the returned scope DOES - * NOT guarantee that members will be contained in a resource (in particular, composed members will not be - * contained in a resource). In turn, this method does not require a context of type {@link MemberAccess}. - *

- * This method can be used if members are only used temporarily for a purpose that does not require proper - * containment of the member, e.g. retrieving the type of a field for validation purposes. There are two reasons - * for using this method: - *

    - *
  1. client code is unable to provide a context of type {@link MemberAccess}, - *
  2. client code wants to invoke {@link IScope#getAllElements()} or similar methods on the returned scope and - * wants to avoid unnecessary caching of all those members. - *
- */ - public def IScope createMemberScopeAllowingNonContainedMembers(TypeRef receiverTypeRef, EObject context, - boolean checkVisibility, boolean staticAccess, boolean structFieldInitMode) { - - val request = new MemberScopeRequest(receiverTypeRef, context, false, checkVisibility, staticAccess, - structFieldInitMode, receiverTypeRef.isDynamic); - return decoratedMemberScopeFor(receiverTypeRef, request); - } - - /** - * Creates member scope via #members and decorates it via #decorate. - */ - private def IScope decoratedMemberScopeFor(TypeRef typeRef, MemberScopeRequest memberScopeRequest) { - if(typeRef === null) - return IScope.NULLSCOPE - var result = members(typeRef, memberScopeRequest); - return result; - } - - /** - * Called only be members functions to decorate returned scope. - */ - private def IScope decorate(IScope scope, MemberScopeRequest memberScopeRequest, TypeRef receiverTypeRef) { - if (scope == IScope.NULLSCOPE) { - return scope; - } - var decoratedScope = scope; - if (memberScopeRequest.checkVisibility && - ! FilterWithErrorMarkerScope.isDecoratedWithFilter(scope, VisibilityAwareMemberScope)) { - decoratedScope = new VisibilityAwareMemberScope(decoratedScope, memberVisibilityChecker, receiverTypeRef, - memberScopeRequest.context); - } - if (memberScopeRequest.staticAccess && - ! FilterWithErrorMarkerScope.isDecoratedWithFilter(scope, StaticWriteAccessFilterScope)) { - decoratedScope = new StaticWriteAccessFilterScope(decoratedScope, memberScopeRequest.context); - } - if (memberScopeRequest.checkVisibility && - ! FilterWithErrorMarkerScope.isDecoratedWithFilter(scope, TypingStrategyAwareMemberScope)) { - decoratedScope = new TypingStrategyAwareMemberScope(decoratedScope, receiverTypeRef, - memberScopeRequest.context); - } - return decoratedScope; - } - - /** - * For the member given by (name, staticAccess) return the erroneous descriptions from the given scope. - *

- * Precondition: {@link #isNonExistentMember} has negative answer. - */ - public def Iterable getErrorsForMember(IScope scope, String memberName, - boolean staticAccess) { - val descriptions = scope.getElements(QualifiedName.create(memberName)) - val errorsOrNulls = descriptions.map[d|IEObjectDescriptionWithError.getDescriptionWithError(d)] - return errorsOrNulls.filterNull - } - - private def dispatch IScope members(TypeRef type, MemberScopeRequest request) { - return IScope.NULLSCOPE - } - - private def dispatch IScope members(UnknownTypeRef type, MemberScopeRequest request) { - return new DynamicPseudoScope() - } - - private def dispatch IScope members(LiteralTypeRef ltr, MemberScopeRequest request) { - throw new UnsupportedOperationException("missing dispatch method for " + ltr.eClass().getName()); - } - - private def dispatch IScope members(BooleanLiteralTypeRef ltr, MemberScopeRequest request) { - val G = request.context.newRuleEnvironment; - return members(G.booleanTypeRef, request); - } - - private def dispatch IScope members(NumericLiteralTypeRef ltr, MemberScopeRequest request) { - val G = request.context.newRuleEnvironment; - return members(G.numberTypeRef, request); // no need to distinguish between number and int - } - - private def dispatch IScope members(StringLiteralTypeRef ltr, MemberScopeRequest request) { - val G = request.context.newRuleEnvironment; - return members(G.stringTypeRef, request); - } - - private def dispatch IScope members(EnumLiteralTypeRef ltr, MemberScopeRequest request) { - val G = request.context.newRuleEnvironment; - return members(N4JSLanguageUtils.getLiteralTypeBase(G, ltr), request); - } - - private def dispatch IScope members(ParameterizedTypeRef ptr, MemberScopeRequest request) { - val IScope result = membersOfType(ptr.declaredType, request); - if (ptr.dynamic && !(result instanceof DynamicPseudoScope)) { - return new DynamicPseudoScope(result.decorate(request, ptr)) - } - return result.decorate(request, ptr) - } - - private def dispatch IScope members(ParameterizedTypeRefStructural ptrs, MemberScopeRequest request) { - val IScope result = membersOfType(ptrs.declaredType, request); - if (ptrs.dynamic && !(result instanceof DynamicPseudoScope)) { - return new DynamicPseudoScope(result.decorate(request, ptrs)) - } - if (ptrs.structuralMembers.empty) { - return result.decorate(request, ptrs) - } - val memberScopeRaw = if (ptrs.structuralType !== null) { - memberScopeFactory.create(result, ptrs.structuralType, request.context, request.staticAccess, - request.structFieldInitMode, request.isDynamicType); - } else { - // note: these are not the members of the defined type - // however, we only scope locally, so that doesn't matter - memberScopeFactory.create(result, ptrs.structuralMembers, request.context, request.staticAccess, - request.structFieldInitMode, request.isDynamicType); - } - - return decorate(memberScopeRaw, request, ptrs); - } - - /** - * Note: N4JSScopeProvider already taking the upper bound before using this class (thus resolving ThisTypeRefs - * beforehand), so we will never enter this method from there; still provided to support uses from other code. - */ - private def dispatch IScope members(ThisTypeRef thisTypeRef, MemberScopeRequest request) { - // taking the upper bound to "resolve" the ThisTypeRef: - // this[C] --> C (ParameterizedTypeRef) - // ~~this[C] with { number prop; } --> ~~C with { number prop; } (ParameterizedTypeRefStructural) - val ub = ts.upperBoundWithReopen(request.context.newRuleEnvironment, thisTypeRef); - - if (ub !== null) { // ThisTypeRef was resolved - return members(ub, request); - } - - // probably an unbound ThisTypeRef or some other error (reported elsewhere) - return IScope.NULLSCOPE; - } - - private def dispatch IScope members(TypeTypeRef ttr, MemberScopeRequest request) { - val MemberScopeRequest staticRequest = request.enforceStatic; - val G = request.context.newRuleEnvironment; - val ctrStaticType = tsh.getStaticType(G, ttr, true); - var IScope staticMembers = membersOfType(ctrStaticType, staticRequest) // staticAccess is always true in this case - if (ctrStaticType instanceof TEnum) { - // enums have their literals as static members - staticMembers = Scopes.scopeFor(ctrStaticType.literals, staticMembers).decorate(request, ttr); - } - if (ttr.dynamic && !(staticMembers instanceof DynamicPseudoScope)) { - staticMembers = new DynamicPseudoScope(staticMembers.decorate(staticRequest, ttr)) - } - // in addition, we need instance members of either Function (in case of constructor{T}) or Object (for type{T}) - // except for @NumberBased/@StringBased enums: - if (ctrStaticType instanceof TEnum && N4JSLanguageUtils.getEnumKind(ctrStaticType as TEnum) !== EnumKind.Normal) { - return staticMembers.decorate(staticRequest, ttr); - } - val MemberScopeRequest instanceRequest = request.enforceInstance; - val builtInScope = BuiltInTypeScope.get(getResourceSet(ttr, request.context)); - val functionType = if (ttr.isConstructorRef) builtInScope.functionType else builtInScope.objectType; - val IScope ftypeScope = membersOfType(functionType, instanceRequest); - val result = CompositeScope.create( - // order matters (shadowing!) - staticMembers.decorate(staticRequest, ttr), - ftypeScope.decorate(instanceRequest, ttr) - ); - return result - } - - private def dispatch IScope members(UnionTypeExpression uniontypeexp, MemberScopeRequest request) { - - if (jsVariantHelper.activateDynamicPseudoScope(request.context)) { // cf. sec. 13.1 - return new DynamicPseudoScope(); - } - - val G = RuleEnvironmentExtensions.newRuleEnvironment(request.context); - var IScope anyPlusScope = null; - val subScopes = new ArrayList(); - for (TypeRef elementTypeRef: uniontypeexp.typeRefs) { - val structFieldInitMode = elementTypeRef.getTypingStrategy() == TypingStrategy.STRUCTURAL_FIELD_INITIALIZER; - val scope = members(elementTypeRef, request.setStructFieldInitMode(structFieldInitMode)); - if (RuleEnvironmentExtensions.isAnyDynamic(G, elementTypeRef)) { - anyPlusScope = scope; - } else { - subScopes.add(scope); - } - } - - val IScope subScope = switch (subScopes.size) { // only create union scope if really necessary, remember this optimization in test, since union{A} tests scope of A only! - case 0: null - case 1: subScopes.get(0) - default: new UnionMemberScope(uniontypeexp, request, subScopes, ts) - } - - if (anyPlusScope === null && subScope === null) { - return IScope.NULLSCOPE; - } - if (anyPlusScope === null) { - return subScope; - } - if (subScope === null) { - return anyPlusScope; - } - return new UberParentScope("", subScope, anyPlusScope); - } - - private def dispatch IScope members(IntersectionTypeExpression intersectiontypeexp, MemberScopeRequest request) { - if (intersectiontypeexp.typeRefs.isEmpty) { - return IScope.NULLSCOPE; - } - - val G = RuleEnvironmentExtensions.newRuleEnvironment(request.context); - var IScope anyPlusScope = null; - val subScopes = new ArrayList(); - for (TypeRef elementTypeRef: intersectiontypeexp.typeRefs) { - val structFieldInitMode = elementTypeRef.getTypingStrategy() == TypingStrategy.STRUCTURAL_FIELD_INITIALIZER; - val scope = members(elementTypeRef, request.setStructFieldInitMode(structFieldInitMode)); - if (RuleEnvironmentExtensions.isAnyDynamic(G, elementTypeRef)) { - anyPlusScope = scope; - } else { - subScopes.add(scope); - } - } - - val IScope subScope = switch (subScopes.size) { // only create union scope if really necessary, remember this optimization in test, since union{A} tests scope of A only! - case 0: null - case 1: subScopes.get(0) - default: new IntersectionMemberScope(intersectiontypeexp, request, subScopes, ts) - } - - if (anyPlusScope === null && subScope === null) { - return IScope.NULLSCOPE; - } - if (subScope !== null) { - return subScope; - } - return anyPlusScope; - } - - private def dispatch IScope members(FunctionTypeRef ftExpr, MemberScopeRequest request) { - return membersOfFunctionTypeRef(ftExpr, request) - } - - private def dispatch IScope members(FunctionTypeExpression ftExpr, MemberScopeRequest request) { - return membersOfFunctionTypeRef(ftExpr, request) - } - - /** delegated from two methods above, to avoid catch-all of ParameterizedTypeRef for FuntionTypeRefs while dispatching */ - def private IScope membersOfFunctionTypeRef(FunctionTypeExprOrRef ftExpr, MemberScopeRequest request) { - val builtInTypeScope = BuiltInTypeScope.get(getResourceSet(ftExpr, request.context)); - val fType = builtInTypeScope.functionType - val ret = membersOfType(fType, request) - return ret.decorate(request, ftExpr); - } - -// TODO member computation should be extracted - private def dispatch IScope membersOfType(Type type, MemberScopeRequest request) { - if (type.eIsProxy) { - return new DynamicPseudoScope() - } - if (jsVariantHelper.activateDynamicPseudoScope(request.context)) { // cf. sec. 13.1 - return new DynamicPseudoScope(); - } - - return IScope.NULLSCOPE - } - - private def dispatch IScope membersOfType(UndefinedType type, MemberScopeRequest request) { - if (jsVariantHelper.activateDynamicPseudoScope(request.context)) { // cf. sec. 13.1 - return new DynamicPseudoScope(); - } - - return IScope.NULLSCOPE - } - - private def dispatch IScope membersOfType(Void type, MemberScopeRequest request) { - return new DynamicPseudoScope() - } - - /** - * Primitive types have no members, but they can be auto-boxed to their - * corresponding object type which then, transparently to the user, provide members. - */ - private def dispatch IScope membersOfType(PrimitiveType prim, MemberScopeRequest request) { - val boxedType = prim.autoboxedType; - return if(boxedType!==null) membersOfType(boxedType, request) else IScope.NULLSCOPE; - } - - /** - * Creates member scope with parent containing members of implicit super types. - */ - private def dispatch IScope membersOfType(ContainerType type, MemberScopeRequest request) { - var parentScope = if (jsVariantHelper.activateDynamicPseudoScope(request.context)) { // cf. sec. 13.1 - new DynamicPseudoScope() - } else { - IScope.NULLSCOPE - }; - - if (!request.staticAccess && type instanceof TClass && N4Scheme.isFromResourceWithN4Scheme(type)) { - // classifiers defined in builtin_js.n4jsd and builtin_n4.n4jsd are allowed to extend primitive - // types, and the following is required to support auto-boxing in such a case: - val rootSuperType = getRootSuperType(type as TClass); - if (rootSuperType instanceof PrimitiveType) { - val boxedType = rootSuperType.autoboxedType; - if(boxedType!==null) { - parentScope = memberScopeFactory.create(parentScope, boxedType, request.context, - request.staticAccess, request.structFieldInitMode, request.isDynamicType); - } - } - } - - return memberScopeFactory.create(parentScope, type, request.context, request.staticAccess, - request.structFieldInitMode, request.isDynamicType); - } - - /** - * Returns a scope of the literals, that is members such as name or value. - * That is, the instance members of an enumeration. The static members are made available - * in {@link #members(EnumTypeRef, EObject, boolean)} - */ - private def dispatch IScope membersOfType(TEnum enumeration, MemberScopeRequest request) { - val builtInTypeScope = BuiltInTypeScope.get(getResourceSet(enumeration, request.context)); - // IDE-1221 select built-in type depending on whether this enumeration is tagged number-/string-based - val enumKind = N4JSLanguageUtils.getEnumKind(enumeration); - val specificEnumType = switch(enumKind) { - case Normal: builtInTypeScope.n4EnumType - case NumberBased: builtInTypeScope.n4NumberBasedEnumType - case StringBased: builtInTypeScope.n4StringBasedEnumType - }; - return membersOfType(specificEnumType, request); - } - - private def dispatch IScope membersOfType(TStructuralType structType, MemberScopeRequest request) { - if (structType.ownedMembers.empty) { - return IScope.NULLSCOPE - } - return memberScopeFactory.create(structType, request.context, request.staticAccess, request.structFieldInitMode, request.isDynamicType); - } - - def private ResourceSet getResourceSet(EObject type, EObject context) { - var result = EcoreUtilN4.getResourceSet(type, context); - if (result === null) - throw new IllegalStateException("type or context must be contained in a ResourceSet") - return result; - } - - def private Type getRootSuperType(TClass type) { - var Type curr = type; - var Type next; - do { - next = if(curr instanceof TClass) curr.superClassRef?.declaredType; - if (next !== null) { - curr = next; - } - } while (next !== null); - return curr; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/ExpressionExtensions.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/ExpressionExtensions.java new file mode 100644 index 0000000000..c858e94f64 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/ExpressionExtensions.java @@ -0,0 +1,135 @@ +/** + * 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.scoping.utils; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.last; + +import java.util.Arrays; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.n4JS.AssignmentExpression; +import org.eclipse.n4js.n4JS.AssignmentOperator; +import org.eclipse.n4js.n4JS.BinaryLogicalExpression; +import org.eclipse.n4js.n4JS.CommaExpression; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.ParenExpression; +import org.eclipse.n4js.n4JS.PostfixExpression; +import org.eclipse.n4js.n4JS.UnaryExpression; +import org.eclipse.n4js.n4JS.UnaryOperator; +import org.eclipse.xtext.EcoreUtil2; + +/** + * General utility methods for expressions. + */ +public class ExpressionExtensions { + + /** + * Returns true if the subExpression actually is the left hand side expression of an assignment expression, that is + * its value could be changed when the assignment is evaluated. I.e. the subExpression could be returned by ancestor + * expression, e.g., if the subExpression is an operand of a binary logical expression. This method returns false, + * if the subExpression is an assignment expression itself! + *

+ * Note that neither assignments nor conditional expression return any of their operands! + */ + public static boolean isLeftHandSide(EObject subExpression) { + if (subExpression == null || subExpression instanceof AssignmentExpression) { + return false; + } + EObject expr = subExpression; + while (expr.eContainer() != null && isPotentialEvalResult(expr.eContainer(), expr)) { + expr = expr.eContainer(); + } + return expr.eContainer() instanceof AssignmentExpression && + ((AssignmentExpression) expr.eContainer()).getLhs() == expr; + } + + /** + * Does the argument occur as operand in a (prefix or postfix) ++ or -- operation? + *

+ * The increment and decrement operators "conceptually" involve both read- and write-access, unlike the LHS of a + * simple assignment. Granted, '+=' and similar compound assignments can be seen as comprising read- and + * write-access. + */ + public static boolean isIncOrDecTarget(EObject subExpression) { + if (subExpression == null || subExpression instanceof AssignmentExpression) { + return false; + } + EObject expr = subExpression; + while (expr.eContainer() != null && isPotentialEvalResult(expr.eContainer(), expr)) { + expr = expr.eContainer(); + } + if (expr.eContainer() instanceof PostfixExpression) { + return true; + } + if (expr.eContainer() instanceof UnaryExpression) { + UnaryExpression ue = (UnaryExpression) expr.eContainer(); + if (ue.getOp() == UnaryOperator.INC || ue.getOp() == UnaryOperator.DEC) { + return true; + } + } + return false; + } + + /***/ + public static boolean isBothReadFromAndWrittenTo(EObject expr) { + if (isLeftHandSide(expr)) { + AssignmentExpression a = EcoreUtil2.getContainerOfType(expr, AssignmentExpression.class); + return a.getOp() != AssignmentOperator.ASSIGN; + } + return isIncOrDecTarget(expr); + } + + /** + * Returns true if the (value of the) subExpression could be returned by container expression, e.g., if the + * subExpression is an operand of a binary logical expression. + *

+ * Most types of expressions return false here. Note that neither assignments nor conditional expression return any + * of their operands, thus, both type of expressions always return false as well. + *

+ * Example: the unparenthesized {@code arr[0]} below {@link #isLeftHandSide} although its direct container isn't an + * assignment but a {@link ParenExpression} + * + *

+	 * {@code
+	 * var arr = [1];
+	 * (arr[0]) = 6;
+	 * console.log(arr[0])
+	 * }
+	 * 
+ */ + private static boolean isPotentialEvalResult(final EObject container, final EObject childExpression) { + if (container instanceof ParenExpression && childExpression instanceof Expression) { + return isPotentialEvalResult((ParenExpression) container, (Expression) childExpression); + } else if (container instanceof BinaryLogicalExpression && childExpression instanceof Expression) { + return isPotentialEvalResult((BinaryLogicalExpression) container, (Expression) childExpression); + } else if (container instanceof CommaExpression && childExpression instanceof Expression) { + return isPotentialEvalResult((CommaExpression) container, (Expression) childExpression); + } else if (container != null && childExpression != null) { + return false; + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(container, childExpression).toString()); + } + } + + private static boolean isPotentialEvalResult(ParenExpression container, Expression childExpression) { + return childExpression != null && childExpression.eContainer() == container; + } + + private static boolean isPotentialEvalResult(BinaryLogicalExpression container, Expression childExpression) { + return childExpression != null && childExpression.eContainer() == container; + } + + private static boolean isPotentialEvalResult(CommaExpression container, Expression childExpression) { + return container != null && last(container.getExprs()) == childExpression; + } + +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/ExpressionExtensions.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/ExpressionExtensions.xtend deleted file mode 100644 index 15b5bb7917..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/ExpressionExtensions.xtend +++ /dev/null @@ -1,118 +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.scoping.utils - -import org.eclipse.n4js.n4JS.AssignmentExpression -import org.eclipse.n4js.n4JS.BinaryLogicalExpression -import org.eclipse.n4js.n4JS.CommaExpression -import org.eclipse.n4js.n4JS.Expression -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.n4JS.ParenExpression -import org.eclipse.n4js.n4JS.PostfixExpression -import org.eclipse.n4js.n4JS.UnaryExpression -import org.eclipse.n4js.n4JS.UnaryOperator -import org.eclipse.n4js.n4JS.AssignmentOperator -import org.eclipse.xtext.EcoreUtil2 - -/** - * General utitility methods for expressions. - */ -class ExpressionExtensions { - - /** - * Returns true if the subExpression actually is the left hand side expression of an assignment expression, that is - * its value could be changed when the assignment is evaluated. I.e. the subExpression - * could be returned by ancestor expression, e.g., if the subExpression is - * an operand of a binary logical expression. - * This method returns false, if the subExpression is an assignment expression itself! - *

- * Note that neither assignments nor conditional expression return any of their operands! - */ - static def boolean isLeftHandSide(EObject subExpression) { - if (subExpression === null || subExpression instanceof AssignmentExpression) { - return false; - } - var EObject expr = subExpression; - while (expr.eContainer!==null && isPotentialEvalResult(expr.eContainer, expr)) { - expr = expr.eContainer; - } - return expr !== null && expr.eContainer instanceof AssignmentExpression && - (expr.eContainer as AssignmentExpression).lhs == expr - } - - - /** - * Does the argument occur as operand in a (prefix or postfix) ++ or -- operation? - *

- * The increment and decrement operators "conceptually" involve both read- and write-access, - * unlike the LHS of a simple assignment. Granted, '+=' and similar compound assignments can be seen - * as comprising read- and write-access. - */ - static def boolean isIncOrDecTarget(EObject subExpression) { - if (subExpression === null || subExpression instanceof AssignmentExpression) { - return false; - } - var EObject expr = subExpression; - while (expr.eContainer!==null && isPotentialEvalResult(expr.eContainer, expr)) { - expr = expr.eContainer; - } - if (expr.eContainer instanceof PostfixExpression) { - return true - } - if (expr.eContainer instanceof UnaryExpression) { - val ue = expr.eContainer as UnaryExpression; - if (ue.op == UnaryOperator.INC || ue.op === UnaryOperator.DEC) { - return true - } - } - return false - } - - static def boolean isBothReadFromAndWrittenTo(EObject expr) { - if (isLeftHandSide(expr)) { - val a = EcoreUtil2.getContainerOfType(expr, AssignmentExpression) - return a.op !== AssignmentOperator.ASSIGN - } - return isIncOrDecTarget(expr) - } - - /** - * Returns true if the (value of the) subExpression could be returned by container expression, e.g., if the subExpression is - * an operand of a binary logical expression. - *

- * Most types of expressions return false here. Note that neither assignments nor conditional expression return any of their operands, thus, both type of expressions - * always return false as well. - *

- * Example: the unparenthesized {@code arr[0]} below {@link #isLeftHandSide} although its direct container isn't an assignment but a {@link ParenExpression} - *

-	 * {@code
-	 * var arr = [1];
-	 * (arr[0]) = 6;
-	 * console.log(arr[0])
-	 * }
-	 * 
- */ - private static def dispatch boolean isPotentialEvalResult(EObject container, EObject childExpression) { - return false; - } - - private static def dispatch boolean isPotentialEvalResult(ParenExpression container, Expression childExpression) { - return childExpression !== null && childExpression.eContainer === container; - } - - private static def dispatch boolean isPotentialEvalResult(BinaryLogicalExpression container, Expression childExpression) { - return childExpression !== null && childExpression.eContainer === container; - } - - private static def dispatch boolean isPotentialEvalResult(CommaExpression container, Expression childExpression) { - return container !== null && container.exprs.last === childExpression; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/LocallyKnownTypesScopingHelper.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/LocallyKnownTypesScopingHelper.java new file mode 100644 index 0000000000..50447bcca7 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/LocallyKnownTypesScopingHelper.java @@ -0,0 +1,187 @@ +/** + * 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.scoping.utils; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; + +import java.util.function.Function; +import java.util.function.Supplier; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.n4JS.N4NamespaceDeclaration; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.resource.N4JSCache; +import org.eclipse.n4js.scoping.N4JSScopeProvider; +import org.eclipse.n4js.scoping.imports.ImportedElementsScopingHelper; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TEnum; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.ts.types.TStructMember; +import org.eclipse.n4js.ts.types.TStructMethod; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.xtext.resource.EObjectDescription; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.scoping.IScope; +import org.eclipse.xtext.scoping.impl.SingletonScope; + +import com.google.common.collect.Iterables; +import com.google.inject.Inject; + +/** + * Helper for {@link N4JSScopeProvider N4JSScopeProvider} using {@link ImportedElementsScopingHelper + * ImportedElementsScopingHelper} for providing scope for types provider. + */ +public class LocallyKnownTypesScopingHelper { + + @Inject + N4JSCache cache; + + @Inject + ImportedElementsScopingHelper importedElementsScopingHelper; + + @Inject + ScopeSnapshotHelper scopeSnapshotHelper; + + /** Returns the type itself and type variables in case the type is generic. */ + public IScope scopeWithTypeAndItsTypeVariables(IScope parent, Type type, boolean staticAccess) { + IScope result = parent; + if (type != null) { + + // add the type itself + if (type.getName() != null && type instanceof TClassifier) { + // note that functions cannot be used as type references + result = new SingletonScope(EObjectDescription.create(type.getName(), type), result); + } + + // add the type variables + if (type.isGeneric()) { + if (type instanceof TClassifier && staticAccess) { + // error case: type variables of a classifier cannot be accessed from static members + // e.g. class C { static x: T; } + // --> return same scope as in success case, but wrap descriptions with a + // WrongStaticAccessorDescription + Function wrapEODs = dscr -> new WrongStaticAccessDescription( + dscr, staticAccess); + result = scopeSnapshotHelper.scopeForEObjects("scopeWithTypeAndItsTypeVariables-1", + type, result, type.getTypeVars(), wrapEODs); + } else { + // success case: simply add type variables to scope + result = scopeSnapshotHelper.scopeForEObjects("scopeWithTypeAndItsTypeVariables-2", type, result, + type.getTypeVars()); + } + } + } + + return result; + } + + /** Returns the type variables if the TStructMethod is generic. */ + public IScope scopeWithTypeVarsOfTStructMethod(IScope parent, TStructMethod m) { + TStructMember mDef = m.getDefinedMember(); + if (mDef instanceof TStructMethod) { + if (((TStructMethod) mDef).isGeneric()) { + return scopeSnapshotHelper.scopeForEObjects("scopeWithTypeVarsOfTStructMethod", mDef, parent, + ((TStructMethod) mDef).getTypeVars()); + } + } + return parent; + } + + /** Returns the type variables if the function type expression is generic. */ + public IScope scopeWithTypeVarsOfFunctionTypeExpression(IScope parent, FunctionTypeExpression funTypeExpr) { + if (funTypeExpr != null && funTypeExpr.isGeneric()) { + return scopeSnapshotHelper.scopeForEObjects("scopeWithTypeVarsOfFunctionTypeExpression", funTypeExpr, + parent, funTypeExpr.getTypeVars()); + } + return parent; + } + + /** Returns scope with locally known types and (as parent) import scope; the result is cached. */ + public IScope scopeWithLocallyDeclaredElems(Script script, Supplier parentSupplier, + boolean onlyNamespacelikes) { + return cache.get(script.eResource(), () -> { + // all types in the index: + IScope parent = parentSupplier.get(); + // but imported types are preferred (or maybe renamed with aliases): + IScope importScope = importedElementsScopingHelper.getImportedTypes(parent, script); + // finally, add locally declared types as the outer scope + IScope localTypes = scopeWithLocallyDeclaredElems(script, importScope, onlyNamespacelikes); + + return localTypes; + }, script, "locallyKnownTypes_" + String.valueOf(onlyNamespacelikes)); + } + + /** Returns scope with locally declared types (without import scope). */ + public IScope scopeWithLocallyDeclaredElems(Script script, IScope parent, boolean onlyNamespacelikes) { + return scopeWithLocallyDeclaredElems(script.getModule(), script, parent, onlyNamespacelikes); + } + + /** Returns scope with locally declared types (without import scope). */ + public IScope scopeWithLocallyDeclaredElems(N4NamespaceDeclaration namespace, IScope parent, + boolean onlyNamespacelikes) { + return cache.get(namespace.eResource(), + () -> scopeWithLocallyDeclaredElems((AbstractNamespace) namespace.getDefinedType(), namespace, parent, + onlyNamespacelikes), + namespace, "scopeWithLocallyDeclaredElems_" + String.valueOf(onlyNamespacelikes)); + } + + /** Returns scope with locally declared types (without import scope). */ + public IScope scopeWithLocallyDeclaredElems(AbstractNamespace namespace, IScope parent, + boolean onlyNamespacelikes) { + return scopeWithLocallyDeclaredElems(namespace, namespace, parent, onlyNamespacelikes); + } + + /** Returns scope with locally declared types (without import scope). */ + private IScope scopeWithLocallyDeclaredElems(AbstractNamespace ans, EObject context, IScope parent, + boolean onlyNamespacelikes) { + if (ans == null || ans.eIsProxy()) { + return parent; + } + Iterable tlElems = onlyNamespacelikes + ? Iterables.concat(ans.getNamespaces(), filter(ans.getTypes(), TEnum.class)) + : filter(ans.getTypes(), t -> !t.isPolyfill()); + Iterable eoDescrs = map(tlElems, + topLevelType -> EObjectDescription.create(topLevelType.getName(), topLevelType)); + return scopeSnapshotHelper.scopeFor("scopeWithLocallyDeclaredTypes", context, parent, eoDescrs); + } + + /** + * Returns scope with locally known types specially configured for super reference in case of polyfill definitions. + * It does not add the polyfillType itself. Instead, only its type variables are added, which are otherwise hidden + * in case of polyfills. The result is not cached as this scope is needed only one time. + */ + public IScope scopeWithLocallyKnownTypesForPolyfillSuperRef(Script script, IScope parent, Type polyfillType) { + + // imported and locally defined types are preferred (or maybe renamed with aliases): + IScope importScope = importedElementsScopingHelper.getImportedTypes(parent, script); + + // locally defined types except polyfillType itself + TModule local = script.getModule(); + Iterable eoDescrs = map(filter(local.getTypes(), t -> t != polyfillType), + t -> EObjectDescription.create(t.getName(), t)); + IScope localTypesScope = scopeSnapshotHelper.scopeFor("scopeWithLocallyKnownTypesForPolyfillSuperRef", script, + importScope, eoDescrs); + + // type variables of polyfill + if (polyfillType != null && polyfillType.isGeneric()) { + return scopeSnapshotHelper.scopeForEObjects("scopeWithLocallyKnownTypesForPolyfillSuperRef-polyfillType", + polyfillType, localTypesScope, polyfillType.getTypeVars()); + } + + // non generic: + return localTypesScope; + + } + +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/LocallyKnownTypesScopingHelper.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/LocallyKnownTypesScopingHelper.xtend deleted file mode 100644 index df9692346a..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/LocallyKnownTypesScopingHelper.xtend +++ /dev/null @@ -1,170 +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.scoping.utils - -import com.google.common.collect.Iterables -import com.google.inject.Inject -import java.util.function.Supplier -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.n4JS.N4NamespaceDeclaration -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.resource.N4JSCache -import org.eclipse.n4js.scoping.N4JSScopeProvider -import org.eclipse.n4js.scoping.imports.ImportedElementsScopingHelper -import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TEnum -import org.eclipse.n4js.ts.types.TStructMethod -import org.eclipse.n4js.ts.types.Type -import org.eclipse.xtext.resource.EObjectDescription -import org.eclipse.xtext.scoping.IScope -import org.eclipse.xtext.scoping.impl.SingletonScope - -/** - * Helper for {@link N4JSScopeProvider N4JSScopeProvider} using - * {@link ImportedElementsScopingHelper ImportedElementsScopingHelper} - * for providing scope for types provider. - */ -class LocallyKnownTypesScopingHelper { - - @Inject - N4JSCache cache - - @Inject - ImportedElementsScopingHelper importedElementsScopingHelper - - @Inject - ScopeSnapshotHelper scopeSnapshotHelper - - /** Returns the type itself and type variables in case the type is generic. */ - def IScope scopeWithTypeAndItsTypeVariables(IScope parent, Type type, boolean staticAccess) { - var IScope result = parent; - if (type !== null) { - - // add the type itself - if (type.name !== null && type instanceof TClassifier) { - // note that functions cannot be used as type references - result = new SingletonScope(EObjectDescription.create(type.name, type), result) - } - - // add the type variables - if (type.generic) { - if (type instanceof TClassifier && staticAccess) { - // error case: type variables of a classifier cannot be accessed from static members - // e.g. class C { static x: T; } - // --> return same scope as in success case, but wrap descriptions with a WrongStaticAccessorDescription - val wrapEODs = [new WrongStaticAccessDescription(it, staticAccess)]; - result = scopeSnapshotHelper.scopeForEObjects("scopeWithTypeAndItsTypeVariables-1", type, result, type.typeVars, wrapEODs); - } else { - // success case: simply add type variables to scope - result = scopeSnapshotHelper.scopeForEObjects("scopeWithTypeAndItsTypeVariables-2", type, result, type.typeVars); - } - } - } - - return result; - } - - /** Returns the type variables if the TStructMethod is generic. */ - def IScope scopeWithTypeVarsOfTStructMethod(IScope parent, TStructMethod m) { - val mDef = m.definedMember - if (mDef instanceof TStructMethod) { - if (mDef.generic) { - return scopeSnapshotHelper.scopeForEObjects("scopeWithTypeVarsOfTStructMethod", mDef, parent, mDef.typeVars); - } - } - return parent; - } - - /** Returns the type variables if the function type expression is generic. */ - def IScope scopeWithTypeVarsOfFunctionTypeExpression(IScope parent, FunctionTypeExpression funTypeExpr) { - if (funTypeExpr !== null && funTypeExpr.generic) { - return scopeSnapshotHelper.scopeForEObjects("scopeWithTypeVarsOfFunctionTypeExpression", funTypeExpr, parent, funTypeExpr.typeVars); - } - return parent; - } - - /** Returns scope with locally known types and (as parent) import scope; the result is cached. */ - def IScope scopeWithLocallyDeclaredElems(Script script, Supplier parentSupplier, boolean onlyNamespacelikes) { - return cache.get(script.eResource, [| - // all types in the index: - val parent = parentSupplier.get(); - // but imported types are preferred (or maybe renamed with aliases): - val IScope importScope = importedElementsScopingHelper.getImportedTypes(parent, script); - // finally, add locally declared types as the outer scope - val localTypes = scopeWithLocallyDeclaredElems(script, importScope, onlyNamespacelikes); - - return localTypes; - ], script, 'locallyKnownTypes_'+String.valueOf(onlyNamespacelikes)); - } - - /** Returns scope with locally declared types (without import scope). */ - def IScope scopeWithLocallyDeclaredElems(Script script, IScope parent, boolean onlyNamespacelikes) { - return scopeWithLocallyDeclaredElems(script.module, script, parent, onlyNamespacelikes); - } - - /** Returns scope with locally declared types (without import scope). */ - def IScope scopeWithLocallyDeclaredElems(N4NamespaceDeclaration namespace, IScope parent, boolean onlyNamespacelikes) { - return cache.get(namespace.eResource, [| - return scopeWithLocallyDeclaredElems(namespace.definedType as AbstractNamespace, namespace, parent, onlyNamespacelikes); - ], namespace, 'scopeWithLocallyDeclaredElems_'+String.valueOf(onlyNamespacelikes)); - } - - /** Returns scope with locally declared types (without import scope). */ - def IScope scopeWithLocallyDeclaredElems(AbstractNamespace namespace, IScope parent, boolean onlyNamespacelikes) { - return scopeWithLocallyDeclaredElems(namespace, namespace, parent, onlyNamespacelikes); - } - - /** Returns scope with locally declared types (without import scope). */ - def private IScope scopeWithLocallyDeclaredElems(AbstractNamespace ans, EObject context, IScope parent, boolean onlyNamespacelikes) { - if (ans === null || ans.eIsProxy) { - return parent; - } - val tlElems = if (onlyNamespacelikes) { - Iterables.concat(ans.namespaces, ans.types.filter[t | t instanceof TEnum ]) - } else { - ans.types.filter[t | !t.polyfill ]; - }; - val eoDescrs = tlElems.map[ topLevelType | - return EObjectDescription.create(topLevelType.name, topLevelType); - ]; - return scopeSnapshotHelper.scopeFor("scopeWithLocallyDeclaredTypes", context, parent, eoDescrs); - } - - /** - * Returns scope with locally known types specially configured for super reference in case of polyfill definitions. - * It is comparable to {@link #getLocallyKnownTypes(Script, EReference, IScopeProvider), but it does not - * add the polyfillType itself. Instead, only its type variables are added, which are otherwise hidden in case of polyfills. - * The result is not cached as this scope is needed only one time. - */ - def IScope scopeWithLocallyKnownTypesForPolyfillSuperRef(Script script, - IScope parent, Type polyfillType) { - - // imported and locally defined types are preferred (or maybe renamed with aliases): - val IScope importScope = importedElementsScopingHelper.getImportedTypes(parent, script) - - // locally defined types except polyfillType itself - val local = script.module - val eoDescrs = local.types.filter[it !== polyfillType].map[EObjectDescription.create(name, it)]; - val IScope localTypesScope = scopeSnapshotHelper.scopeFor("scopeWithLocallyKnownTypesForPolyfillSuperRef", script, importScope, eoDescrs); - - // type variables of polyfill - if (polyfillType !== null && polyfillType.generic) { - return scopeSnapshotHelper.scopeForEObjects("scopeWithLocallyKnownTypesForPolyfillSuperRef-polyfillType", polyfillType, localTypesScope, polyfillType.typeVars); - } - - // non generic: - return localTypesScope; - - } - -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/ScopesHelper.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/ScopesHelper.java new file mode 100644 index 0000000000..16cc6b8992 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/ScopesHelper.java @@ -0,0 +1,94 @@ +/** + * 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.scoping.utils; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.validation.JavaScriptVariantHelper; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.scoping.IScope; +import org.eclipse.xtext.scoping.Scopes; +import org.eclipse.xtext.scoping.impl.MapBasedScope; +import org.eclipse.xtext.scoping.impl.MultimapBasedScope; +import org.eclipse.xtext.scoping.impl.SimpleScope; +import org.eclipse.xtext.util.SimpleAttributeResolver; + +import com.google.common.base.Function; +import com.google.common.collect.Iterables; +import com.google.inject.Inject; + +/** + * Some utility methods, similar to xtext's {@link Scopes}. + */ +public class ScopesHelper { + + @Inject + private JavaScriptVariantHelper javaScriptVariantHelper; + + /** + * Creates a map based scope for the given iterable of descriptions. + * + * @param context + * The context of the scope + * @param descriptions + * The descriptions + */ + public IScope mapBasedScopeFor(EObject context, Iterable descriptions) { + return mapBasedScopeFor(context, IScope.NULLSCOPE, descriptions); + } + + /** + * Creates a map based scope for the given iterable of descriptions. + * + * @param context + * The context of the scope + * @param parent + * The parent scope + * @param descriptions + * The descriptions + */ + public IScope mapBasedScopeFor(EObject context, IScope parent, Iterable descriptions) { + if (javaScriptVariantHelper.isMultiQNScope(context)) { + return MultimapBasedScope.createScope(parent, descriptions, false); + } else { + return MapBasedScope.createScope(parent, descriptions); + } + } + + /** + * Convenience method for {@link #scopeFor(Iterable,Function,Function,IScope)}. + */ + public IScope scopeFor(Iterable elements, + Function wrapper) { + return scopeFor(elements, wrapper, IScope.NULLSCOPE); + } + + /** + * Convenience method for {@link #scopeFor(Iterable,Function,Function,IScope)}. + */ + public IScope scopeFor(Iterable elements, + Function wrapper, IScope outer) { + return scopeFor(elements, QualifiedName.wrapper(SimpleAttributeResolver.NAME_RESOLVER), wrapper, outer); + } + + /** + * Similar to {@link Scopes#scopeFor(Iterable,Function,IScope)} but supports custom wrapping of the + * IEObjectDescriptions, for example to wrap them with error message providing subclasses such as + * {@link WrongStaticAccessDescription}. The wrapper can return the object description unchanged, create and return + * a new one or may return null to remove the corresponding object from the scope. + */ + public IScope scopeFor(Iterable elements, + Function nameComputation, + Function wrapper, IScope outer) { + return new SimpleScope(outer, + Iterables.transform(Scopes.scopedElementsFor(elements, nameComputation), wrapper)); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/ScopesHelper.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/ScopesHelper.xtend deleted file mode 100644 index 3975a05c70..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/utils/ScopesHelper.xtend +++ /dev/null @@ -1,89 +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.scoping.utils - -import com.google.common.base.Function -import com.google.common.collect.Iterables -import org.eclipse.emf.ecore.EObject -import org.eclipse.xtext.naming.QualifiedName -import org.eclipse.xtext.resource.IEObjectDescription -import org.eclipse.xtext.scoping.IScope -import org.eclipse.xtext.scoping.Scopes -import org.eclipse.xtext.scoping.impl.SimpleScope -import org.eclipse.xtext.util.SimpleAttributeResolver -import com.google.inject.Inject -import org.eclipse.n4js.validation.JavaScriptVariantHelper -import org.eclipse.xtext.scoping.impl.MapBasedScope -import org.eclipse.xtext.scoping.impl.MultimapBasedScope - -/** - * Some utility methods, similar to xtext's {@link Scopes}. - */ -public class ScopesHelper { - - @Inject - private JavaScriptVariantHelper javaScriptVariantHelper; - - /** - * Creates a map based scope for the given iterable of descriptions. - * - * @param context The context of the scope - * @param descriptions The descriptions - */ - def public IScope mapBasedScopeFor(EObject context, Iterable descriptions) { - return mapBasedScopeFor(context, IScope.NULLSCOPE, descriptions); - } - - /** - * Creates a map based scope for the given iterable of descriptions. - * - * @param context The context of the scope - * @param parent The parent scope - * @param descriptions The descriptions - */ - def public IScope mapBasedScopeFor(EObject context, IScope parent, Iterable descriptions) { - if (javaScriptVariantHelper.isMultiQNScope(context)) { - return MultimapBasedScope.createScope(parent, descriptions, false); - } else { - return MapBasedScope.createScope(parent, descriptions); - } - } - - /** - * Convenience method for {@link #scopeFor(Iterable,Function,Function,IScope)}. - */ - def public IScope scopeFor(Iterable elements, - Function wrapper) { - return scopeFor(elements, wrapper, IScope.NULLSCOPE); - } - - /** - * Convenience method for {@link #scopeFor(Iterable,Function,Function,IScope)}. - */ - def public IScope scopeFor(Iterable elements, - Function wrapper, IScope outer) { - return scopeFor(elements, QualifiedName.wrapper(SimpleAttributeResolver.NAME_RESOLVER), wrapper, outer); - } - - /** - * Similar to {@link Scopes#scopeFor(Iterable,Function,IScope)} but supports custom wrapping - * of the IEObjectDescriptions, for example to wrap them with error message providing subclasses - * such as {@link WrongStaticAccessDescription}. The wrapper can return the object description - * unchanged, create and return a new one or may return null to remove the corresponding object - * from the scope. - */ - def public IScope scopeFor(Iterable elements, - Function nameComputation, - Function wrapper, IScope outer) { - return new SimpleScope(outer, - Iterables.transform(Scopes.scopedElementsFor(elements, nameComputation), wrapper)); - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/DIUtility.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/DIUtility.java new file mode 100644 index 0000000000..f91adc4332 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/DIUtility.java @@ -0,0 +1,189 @@ +/** + * 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.tooling.organizeImports; + +import static org.eclipse.n4js.AnnotationDefinition.BINDER; +import static org.eclipse.n4js.AnnotationDefinition.GENERATE_INJECTOR; +import static org.eclipse.n4js.AnnotationDefinition.INJECT; +import static org.eclipse.n4js.AnnotationDefinition.USE_BINDER; +import static org.eclipse.n4js.AnnotationDefinition.WITH_PARENT_INJECTOR; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.n4ProviderType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.newRuleEnvironment; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.TAnnotation; +import org.eclipse.n4js.ts.types.TAnnotationArgument; +import org.eclipse.n4js.ts.types.TAnnotationTypeRefArgument; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.typesystem.utils.AllSuperTypesCollector; +import org.eclipse.n4js.utils.DeclMergingHelper; + +/** + * Collection of utility methods for working with N4JS DI. + */ +public class DIUtility { + + /***/ + public static boolean isSingleton(Type type) { + return exists(type.getAnnotations(), a -> AnnotationDefinition.SINGLETON.name.equals(a.getName())); + } + + /***/ + public static boolean hasSuperType(Type type) { + if (type instanceof TClass) { + TClass tc = (TClass) type; + if (tc.getSuperClassRef() != null) { + return tc.getSuperClassRef().getDeclaredType() instanceof TClass; + } + } + return false; + } + + /** + * Returns true if provided type is instanceof {@link TClass} and at least owned member is annotated with + * {@link AnnotationDefinition#INJECT}. + */ + public static boolean hasInjectedMembers(Type type, DeclMergingHelper declMergingHelper) { + if (type instanceof TClass) { + return exists(AllSuperTypesCollector.collect((TClass) type, declMergingHelper), + t -> exists(t.getOwnedMembers(), m -> INJECT.hasAnnotation(m))); + } + return false; + } + + /** + * Generate DI meta info for classes that have injected members, or are can be injected and have DI relevant + * information, e.g. scope annotation. Also if type has a super type (injection of inherited members) + */ + public static boolean isInjectedClass(Type type, DeclMergingHelper declMergingHelper) { + return isSingleton(type) || hasInjectedMembers(type, declMergingHelper) || hasSuperType(type); + } + + /***/ + public static boolean isBinder(Type type) { + return BINDER.hasAnnotation(type); + } + + /***/ + public static boolean isDIComponent(Type type) { + return GENERATE_INJECTOR.hasAnnotation(type); + } + + /** + * Checks if diComponent has parent component, that is one specified by the superclass or by the inheritance. + */ + public static boolean hasParentInjector(Type type) { + if (WITH_PARENT_INJECTOR.hasAnnotation(type)) { + return true; + } + + if (type instanceof TClass) { + return ((TClass) type).getSuperClassRef() != null; + } else + return false; + } + + /** + * @returns {@link Type} of the parent DIComponent. Throws {@link RuntimeException} if no parent on provided type. + */ + public static Type findParentDIC(Type type) { + TypeRef parent = null; + if (WITH_PARENT_INJECTOR.hasAnnotation(type)) { + TAnnotation ann = WITH_PARENT_INJECTOR.getOwnedAnnotation(type); + if (ann != null) { + parent = ((TAnnotationTypeRefArgument) ann.getArgs().get(0)).getTypeRef(); + } + + } else if (type instanceof TClass) { + parent = ((TClass) type).getSuperClassRef(); + } + + if (parent != null) { + return parent.getDeclaredType(); + } + + throw new RuntimeException("no parent on " + type.getName()); + } + + /** + * returns list of types that are parameters of {@link AnnotationDefinition#USE_BINDER} annotations attached to a + * given type or empty list + */ + public static List resolveBinders(Type type) { + List argTypes = new ArrayList<>(); + for (TAnnotation ann : USE_BINDER.getAllOwnedAnnotations(type)) { + for (TAnnotationArgument annArg : ann.getArgs()) { + Type argType = ((TAnnotationTypeRefArgument) annArg).getTypeRef().getDeclaredType(); + if (argType != null) { + argTypes.add(argType); + } + } + } + + return argTypes; + } + + /** + * Returns with {@code true} if one or more members of the given type are annotated with {@code @Inject} annotation. + */ + public static boolean requiresInjection(Type type, DeclMergingHelper declMergingHelper) { + if (type instanceof TClass) { + return exists(AllSuperTypesCollector.collect((TClass) type, declMergingHelper), + tc -> exists(tc.getOwnedMembers(), m -> INJECT.hasAnnotation(m))); + } + return false; + } + + /** + * Returns with {@code true} if the type reference argument requires injection. Either the declared type requires + * injection, or the type reference represents an N4 provider, and the dependency of the provider requires + * injection. Otherwise returns with {@code false}. + */ + public static boolean requiresInjection(TypeRef tr, DeclMergingHelper declMergingHelper) { + return requiresInjection(tr.getDeclaredType(), declMergingHelper) + || isProviderType(tr) && requiresInjection(getProvidedType(tr), declMergingHelper); + } + + /** + * Returns with {@code true} if the type reference argument is an N4 provider. Otherwise returns with {@code false}. + * Also returns with {@code false} if the type reference is sub interface of N4 provider or a class which implements + * the N4 provider interface. + */ + public static boolean isProviderType(TypeRef tr) { + return null != tr && tr.getDeclaredType() == n4ProviderType(newRuleEnvironment(tr)); + } + + /** + * Returns with the type most nested dependency if the type reference argument represents and N4 provider. Otherwise + * returns with {@code null}. + */ + public static Type getProvidedType(TypeRef tr) { + if (!isProviderType(tr)) { + return null; + } + TypeRef nestedTypeRef = tr; + while (isProviderType(nestedTypeRef) && nestedTypeRef instanceof ParameterizedTypeRef) { + List typeArgs = toList( + filter(((ParameterizedTypeRef) nestedTypeRef).getDeclaredTypeArgs(), TypeRef.class)); + nestedTypeRef = (typeArgs.isEmpty()) ? null : typeArgs.get(0); + } + return nestedTypeRef == null ? null : nestedTypeRef.getDeclaredType(); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/DIUtility.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/DIUtility.xtend deleted file mode 100644 index 72376157a3..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/DIUtility.xtend +++ /dev/null @@ -1,151 +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.tooling.organizeImports - -import java.util.List -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.TAnnotationTypeRefArgument -import org.eclipse.n4js.ts.types.TClass -import org.eclipse.n4js.ts.types.Type -import org.eclipse.n4js.typesystem.utils.AllSuperTypesCollector -import org.eclipse.n4js.utils.DeclMergingHelper - -import static org.eclipse.n4js.AnnotationDefinition.* - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * Collection of utility methods for working with N4JS DI. - */ -class DIUtility { - - def public static boolean isSingleton(Type type) { - type.annotations.exists[name == AnnotationDefinition.SINGLETON.name] - } - - def public static boolean hasSuperType(Type type) { - if(type instanceof TClass){ - type.superClassRef?.declaredType instanceof TClass - }else{ - false - } - } - - /** - * Returns true if provided type is instanceof {@link TClass} - * and at least owned member is annotated with {@link AnnotationDefinition#INJECT}. - */ - def public static boolean hasInjectedMembers(Type type, DeclMergingHelper declMergingHelper) { - if (type instanceof TClass) - AllSuperTypesCollector.collect(type, declMergingHelper).exists[ownedMembers.exists[INJECT.hasAnnotation(it)]] - else - false; - } - - /** - * Generate DI meta info for classes that have injected members, - * or are can be injected and have DI relevant information, e.g. scope annotation. - * Also if type has a super type (injection of inherited members) - */ - def public static boolean isInjectedClass(Type it, DeclMergingHelper declMergingHelper){ - isSingleton || hasInjectedMembers(declMergingHelper) || hasSuperType - } - - def public static boolean isBinder(Type type) { - BINDER.hasAnnotation(type) - } - - def public static boolean isDIComponent(Type type) { - GENERATE_INJECTOR.hasAnnotation(type) - } - - /** - * Checks if diComponent has parent component, that is one specified by the superclass - * or by the inheritance. - */ - def public static boolean hasParentInjector(Type type) { - return WITH_PARENT_INJECTOR.hasAnnotation(type) || - if (type instanceof TClass) { - type.superClassRef !== null; - } else - false; - } - - /** - * @returns {@link Type} of the parent DIComponent. - * @throws {@link RuntimeException} if no parent on provided type. - */ - def public static Type findParentDIC(Type type) { - var TypeRef parent = if (WITH_PARENT_INJECTOR.hasAnnotation(type)) { - (WITH_PARENT_INJECTOR.getOwnedAnnotation(type)?.args.head as TAnnotationTypeRefArgument).typeRef; - } else if (type instanceof TClass) { - type.superClassRef; - } else - null; - - if (parent !== null) { - return parent.declaredType - } - - throw new RuntimeException("no parent on " + type.name); - } - - /** - * returns list of types that are parameters of {@link AnnotationDefinition#USE_BINDER} annotations - * attached to a given type or empty list - */ - def public static List resolveBinders(Type type) { - return USE_BINDER.getAllOwnedAnnotations(type) - .map[args].flatten - .map[(it as TAnnotationTypeRefArgument).typeRef.declaredType].filterNull.toList - } - - /** Returns with {@code true} if one or more members of the given type are annotated with {@code @Inject} annotation. */ - def public static boolean requiresInjection(Type type, DeclMergingHelper declMergingHelper) { - return if (type instanceof TClass) AllSuperTypesCollector.collect(type, declMergingHelper).exists[ownedMembers.exists[INJECT.hasAnnotation(it)]] else false; - } - - /** - * Returns with {@code true} if the type reference argument requires injection. Either the declared type requires injection, - * or the type reference represents an N4 provider, and the dependency of the provider requires injection. Otherwise - * returns with {@code false}. - */ - def public static boolean requiresInjection(TypeRef it, DeclMergingHelper declMergingHelper) { - return declaredType.requiresInjection(declMergingHelper) || (providerType && providedType.requiresInjection(declMergingHelper)) - } - - /** - * Returns with {@code true} if the type reference argument is an N4 provider. Otherwise returns with {@code false}. - * Also returns with {@code false} if the type reference is sub interface of N4 provider or a class which implements - * the N4 provider interface. - */ - def public static boolean isProviderType(TypeRef it) { - null !== it && declaredType === newRuleEnvironment.n4ProviderType; - } - - /** - * Returns with the type most nested dependency if the type reference argument represents and N4 provider. - * Otherwise returns with {@code null}. - */ - def public static getProvidedType(TypeRef it) { - if (!providerType) { - return null; - } - var nestedTypeRef = it; - while (nestedTypeRef.providerType && nestedTypeRef instanceof ParameterizedTypeRef) { - val typeArgs = (nestedTypeRef as ParameterizedTypeRef).declaredTypeArgs.filter(TypeRef); - nestedTypeRef = if (typeArgs.nullOrEmpty) null else typeArgs.head; - } - return nestedTypeRef?.declaredType; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportSpecifiersUtil.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportSpecifiersUtil.java new file mode 100644 index 0000000000..f516e206e5 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportSpecifiersUtil.java @@ -0,0 +1,209 @@ +/** + * Copyright (c) 2017 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.tooling.organizeImports; + +import static com.google.common.base.Strings.isNullOrEmpty; + +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 java.util.function.Consumer; + +import org.eclipse.n4js.N4JSLanguageConstants; +import org.eclipse.n4js.n4JS.DefaultImportSpecifier; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.ElementExportDefinition; +import org.eclipse.n4js.ts.types.ExportDefinition; +import org.eclipse.n4js.ts.types.ModuleExportDefinition; +import org.eclipse.n4js.ts.types.TExportableElement; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.RecursionGuard; + +import com.google.common.collect.Lists; + +/** + * Utilities for ImportSpecifiers + */ +public class ImportSpecifiersUtil { + + /** + * @return {@link List} of {@link ImportProvidedElement}s describing imported elements + */ + public static List mapToImportProvidedElements( + Collection importSpecifiers) { + + List result = new ArrayList<>(); + for (ImportSpecifier specifier : importSpecifiers) { + if (specifier instanceof NamespaceImportSpecifier) { + result.addAll(namespaceToProvidedElements((NamespaceImportSpecifier) specifier)); + } else if (specifier instanceof NamedImportSpecifier) { + NamedImportSpecifier nis = (NamedImportSpecifier) specifier; + result.add(new ImportProvidedElement(usedName(nis), importedElementName(nis), + nis, N4JSLanguageUtils.isHollowElement(nis.getImportedElement()))); + } + } + + return result; + } + + /** Map all exported elements from namespace target module to the import provided elements. */ + private static List namespaceToProvidedElements(NamespaceImportSpecifier specifier) { + TModule importedModule = importedModule(specifier); + if (importedModule == null || importedModule.eIsProxy()) { + return Collections.emptyList(); + } + + List importProvidedElements = new ArrayList<>(); + // add import provided element for a namespace itself + importProvidedElements.add(new ImportProvidedElement(specifier.getAlias(), + computeNamespaceActualName(specifier), specifier, false)); + + Set localNamesAdded = new HashSet<>(); + collectProvidedElements(importedModule, new RecursionGuard<>(), exportDef -> { + String localName = importedElementName(specifier, exportDef); + // function overloading and declaration merging in .d.ts can lead to multiple elements of same name + // being imported via a single namespace import -> to avoid showing bogus "duplicate import" errors + // in those cases we need to avoid adding more than one ImportProvidedElement in those cases: + // TODO IDE-3604 no longer required for function overloading; should probably be removed once declaration + // merging is supported + if (localNamesAdded.add(localName)) { + importProvidedElements.add( + new ImportProvidedElement(localName, exportDef.getExportedName(), + specifier, N4JSLanguageUtils.isHollowElement(exportDef.getExportedElement()))); + } + }); + + return importProvidedElements; + } + + private static void collectProvidedElements(AbstractNamespace namespace, RecursionGuard guard, + Consumer consumer) { + for (ExportDefinition exportDef : Lists.reverse(namespace.getExportDefinitions())) { + if (exportDef instanceof ElementExportDefinition) { + consumer.accept((ElementExportDefinition) exportDef); + } else if (exportDef instanceof ModuleExportDefinition) { + TModule exportedModule = ((ModuleExportDefinition) exportDef).getExportedModule(); + if (exportedModule != null && !exportedModule.eIsProxy()) { + if (guard.tryNext(exportedModule)) { + collectProvidedElements(exportedModule, guard, consumer); + } + } + } + } + } + + /** + * Computes 'actual' name of the namespace for {@link ImportProvidedElement} entry. If processed namespace refers to + * unresolved module, will return dummy name, otherwise returns artificial name composed of prefix and target module + * qualified name + * + */ + public static String computeNamespaceActualName(NamespaceImportSpecifier specifier) { + if (importedModule(specifier).eIsProxy()) + return ImportProvidedElement.NAMESPACE_PREFIX + specifier.hashCode(); + else + return ImportProvidedElement.NAMESPACE_PREFIX + importedModule(specifier).getQualifiedName().toString(); + } + + /** + * Computes exported name of the element imported by this specifier. + */ + public static String importedElementName(NamedImportSpecifier specifier) { + if (specifier instanceof DefaultImportSpecifier) { + return N4JSLanguageConstants.EXPORT_DEFAULT_NAME; + } + + TExportableElement element = specifier.getImportedElement(); + if (element == null) + return "<" + specifier.getImportedElementAsText() + ">(null)"; + + if (element.eIsProxy()) { + if (specifier.isDeclaredDynamic()) { + return specifier.getImportedElementAsText(); + } + return "<" + specifier.getImportedElementAsText() + ">(proxy)"; + } + + return specifier.getImportedElementAsText(); + } + + /** returns locally used name of element imported via {@link NamedImportSpecifier} */ + public static String usedName(NamedImportSpecifier nis) { + return (nis.getAlias() == null) ? importedElementName(nis) : nis.getAlias(); + } + + /** returns locally used name of element imported via {@link NamespaceImportSpecifier} */ + public static String importedElementName(NamespaceImportSpecifier is, ElementExportDefinition exportDef) { + return is.getAlias() + "." + exportDef.getExportedName(); + } + + /***/ + public static TModule importedModule(ImportSpecifier is) { + return ((ImportDeclaration) is.eContainer()).getModule(); + } + + /** + * Returns true if the module that is target of the import declaration containing provided import specifier is + * invalid (null, proxy, no name). Additionally for {@link NamedImportSpecifier} instances checks if linker failed + * to resolve target (is null, proxy, or has no name) + * + * @param spec + * - the ImportSpecifier to investigate + * @return true import looks broken + */ + public static boolean isBrokenImport(ImportSpecifier spec) { + return isBrokenImport((ImportDeclaration) spec.eContainer(), spec); + } + + /** + * Returns true iff the target module of the given import declaration is invalid (null, proxy, no name). Import + * specifiers are not checked. + */ + public static boolean isBrokenImport(ImportDeclaration decl) { + return isBrokenImport(decl, null); + } + + private static boolean isBrokenImport(ImportDeclaration decl, ImportSpecifier spec) { + TModule module = decl.getModule(); + + // check target module + if (module == null || module.eIsProxy() || isNullOrEmpty(module.getQualifiedName())) { + return true; + } + + // check import specifier + if (spec instanceof NamedImportSpecifier && !spec.isDeclaredDynamic()) { + NamedImportSpecifier nis = (NamedImportSpecifier) spec; + if (nis.eIsProxy() || isNullOrEmpty(nis.getImportedElementAsText())) { + return true; + } + + // check what object that is linked + TExportableElement imported = nis.getImportedElement(); + if (imported == null) { + return true; + } + if (imported.eIsProxy()) { + return true; + } + } + + return false; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportSpecifiersUtil.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportSpecifiersUtil.xtend deleted file mode 100644 index 1fcdef565f..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportSpecifiersUtil.xtend +++ /dev/null @@ -1,191 +0,0 @@ -/** - * Copyright (c) 2017 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.tooling.organizeImports - -import com.google.common.collect.Lists -import java.util.List -import java.util.function.Consumer -import org.eclipse.n4js.N4JSLanguageConstants -import org.eclipse.n4js.n4JS.DefaultImportSpecifier -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.ImportSpecifier -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.ElementExportDefinition -import org.eclipse.n4js.ts.types.ModuleExportDefinition -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.RecursionGuard -import org.eclipse.n4js.validation.JavaScriptVariantHelper - -/** - * Utilities for ImportSpecifiers - */ -class ImportSpecifiersUtil { - - /** - * @return {@link List} of {@link ImportProvidedElement}s describing imported elements - */ - public static def List mapToImportProvidedElements( - List importSpecifiers, JavaScriptVariantHelper jsVariantHelper - ) { - return importSpecifiers.map( - specifier | - switch (specifier) { - NamespaceImportSpecifier: - return namespaceToProvidedElements(jsVariantHelper, specifier) - NamedImportSpecifier: - return newArrayList( - new ImportProvidedElement(specifier.usedName, specifier.importedElementName, - specifier as ImportSpecifier, - N4JSLanguageUtils.isHollowElement(specifier.importedElement))) - default: - return emptyList - } - ).flatten.toList - } - - /** Map all exported elements from namespace target module to the import provided elements. */ - private static def List namespaceToProvidedElements(JavaScriptVariantHelper jsVariantHelper, NamespaceImportSpecifier specifier) { - val importedModule = specifier.importedModule; - if (importedModule === null || importedModule.eIsProxy) - return emptyList - - val importProvidedElements = newArrayList - // add import provided element for a namespace itself - importProvidedElements.add(new ImportProvidedElement(specifier.alias, - computeNamespaceActualName(specifier), specifier, false)); - - val localNamesAdded = newHashSet; - collectProvidedElements(importedModule, new RecursionGuard(), [ exportDef | - val localName = specifier.importedElementName(exportDef); - // function overloading and declaration merging in .d.ts can lead to multiple elements of same name - // being imported via a single namespace import -> to avoid showing bogus "duplicate import" errors - // in those cases we need to avoid adding more than one ImportProvidedElement in those cases: - // TODO IDE-3604 no longer required for function overloading; should probably be removed once declaration merging is supported - if (localNamesAdded.add(localName)) { - importProvidedElements.add( - new ImportProvidedElement(localName, exportDef.exportedName, - specifier, N4JSLanguageUtils.isHollowElement(exportDef.exportedElement))); - } - ]); - - return importProvidedElements - } - - private static def void collectProvidedElements(AbstractNamespace namespace, RecursionGuard guard, Consumer consumer) { - for (exportDef : Lists.reverse(namespace.exportDefinitions)) { - if (exportDef instanceof ElementExportDefinition) { - consumer.accept(exportDef); - } else if (exportDef instanceof ModuleExportDefinition) { - val exportedModule = exportDef.exportedModule; - if (exportedModule !== null && !exportedModule.eIsProxy) { - if (guard.tryNext(exportedModule)) { - collectProvidedElements(exportedModule, guard, consumer); - } - } - } - } - } - - /** - * Computes 'actual' name of the namespace for {@link ImportProvidedElement} entry. - * If processed namespace refers to unresolved module, will return dummy name, - * otherwise returns artificial name composed of prefix and target module qualified name - * - */ - public static def String computeNamespaceActualName(NamespaceImportSpecifier specifier) { - if (specifier.importedModule.eIsProxy) - ImportProvidedElement.NAMESPACE_PREFIX + specifier.hashCode - else - ImportProvidedElement.NAMESPACE_PREFIX + specifier.importedModule.qualifiedName.toString - } - - /** - * Computes exported name of the element imported by this specifier. - */ - public static def String importedElementName(NamedImportSpecifier specifier) { - if (specifier instanceof DefaultImportSpecifier) { - return N4JSLanguageConstants.EXPORT_DEFAULT_NAME; - } - - val element = specifier.importedElement - if (element === null) - return "<" + specifier.importedElementAsText + ">(null)" - - if (element.eIsProxy) { - if (specifier.declaredDynamic) { - return specifier.importedElementAsText; - } - return "<" + specifier.importedElementAsText + ">(proxy)" - } - - return specifier.importedElementAsText; - } - - /** returns locally used name of element imported via {@link NamedImportSpecifier} */ - public static def String usedName(NamedImportSpecifier it) { - if (alias === null) importedElementName else alias - } - - /** returns locally used name of element imported via {@link NamespaceImportSpecifier} */ - public static def String importedElementName(NamespaceImportSpecifier is, ElementExportDefinition exportDef) { - is.alias + "." + exportDef.exportedName - } - - public static def TModule importedModule(ImportSpecifier it) { - (eContainer as ImportDeclaration).module - } - - /** - * Returns true if the module that is target of the import declaration containing provided import specifier is invalid (null, proxy, no name). - * Additionally for {@link NamedImportSpecifier} instances checks if linker failed to resolve target (is null, proxy, or has no name) - * - * @param spec - the ImportSpecifier to investigate - * @return true import looks broken - * */ - public static def boolean isBrokenImport(ImportSpecifier spec) { - return isBrokenImport(spec.eContainer as ImportDeclaration, spec); - } - - /** - * Returns true iff the target module of the given import declaration is invalid (null, proxy, no name). - * Import specifiers are not checked. - */ - public static def boolean isBrokenImport(ImportDeclaration decl) { - return isBrokenImport(decl, null); - } - - private static def boolean isBrokenImport(ImportDeclaration decl, ImportSpecifier spec) { - val module = decl.module; - - // check target module - if (module === null || module.eIsProxy || module.qualifiedName.isNullOrEmpty) - return true - - // check import specifier - if (spec instanceof NamedImportSpecifier && !spec.declaredDynamic) { - val nis = spec as NamedImportSpecifier; - if (nis === null || nis.eIsProxy || nis.importedElementAsText.isNullOrEmpty) - return true - - // check what object that is linked - val imported = nis.importedElement - if (imported === null) - return true - if (imported.eIsProxy) - return true - } - - return false - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportStateCalculator.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportStateCalculator.java new file mode 100644 index 0000000000..d88111b6fa --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportStateCalculator.java @@ -0,0 +1,318 @@ +/** + * 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.tooling.organizeImports; + +import static org.eclipse.n4js.tooling.organizeImports.ImportSpecifiersUtil.computeNamespaceActualName; +import static org.eclipse.n4js.tooling.organizeImports.ImportSpecifiersUtil.isBrokenImport; +import static org.eclipse.n4js.tooling.organizeImports.ImportSpecifiersUtil.mapToImportProvidedElements; +import static org.eclipse.n4js.tooling.organizeImports.ScriptDependencyResolver.usedDependenciesTypeRefs; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.flatten; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.forEach; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.head; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.tail; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.postprocessing.ASTMetaInfoCache; +import org.eclipse.n4js.resource.N4JSResource; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.validation.IssueCodes; +import org.eclipse.xtext.xbase.lib.Pair; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; + +/** + * Analyzes all imports in a script. Builds up a data structure of {@link RecordingImportState} to capture the findings. + */ +public class ImportStateCalculator { + + /** + * Algorithm to check the Model for Issues with Imports. + * + * @returns {@link RecordingImportState} + */ + public RecordingImportState calculateImportstate(Script script) { + RecordingImportState reg = new RecordingImportState(); + + ASTMetaInfoCache astMetaInfoCache = ((N4JSResource) script.eResource()).getASTMetaInfoCacheVerifyContext(); + + // Calculate Available + Iterable importDeclarationsALL = filter(script.getScriptElements(), ImportDeclaration.class); + + registerDuplicatedImoprtDeclarationsFrom(reg, importDeclarationsALL); + + List importSpecifiersUnAnalyzed = toList( + flatten(map(filter(importDeclarationsALL, it -> !reg.isDuplicatingImportDeclaration(it)), + decl -> decl.getImportSpecifiers()))); + + // markDuplicatingSpecifiersAsUnused(importSpecifiersUnAnalyzed) + + // collect all unused if stable + registerUnusedAndBrokenImports(reg, importSpecifiersUnAnalyzed, astMetaInfoCache); + + List importProvidedElements = mapToImportProvidedElements( + toList(filter(importSpecifiersUnAnalyzed, it -> !(reg.brokenImports.contains(it))))); + + // refactor into specific types, those are essentially Maps holding elements in insertion order (keys and + // values) + List>> lN2IPE = new ArrayList<>(); + List>> lM2IPE = new ArrayList<>(); + + // TODO refactor this, those composed collections should be encapsulated as specific types with proper get/set + // methods + for (ImportProvidedElement ipe : importProvidedElements) { + Pair> pN2IPE = findFirst(lN2IPE, + p -> Objects.equals(p.getKey(), ipe.getCollisionUniqueName())); + + if (pN2IPE != null) { + pN2IPE.getValue().add(ipe); + } else { + List al = new ArrayList<>(); + al.add(ipe); + lN2IPE.add(Pair.of(ipe.getCollisionUniqueName(), al)); + } + Pair> pM2IPE = findFirst(lM2IPE, + p -> Objects.equals(p.getKey(), ipe.getImportedModule())); + if (pM2IPE != null) { + pM2IPE.getValue().add(ipe); + } else { + List al = new ArrayList<>(); + al.add(ipe); + lM2IPE.add(Pair.of(ipe.getImportedModule(), al)); + } + } + + registerUsedImportsLocalNamesCollisions(reg, lN2IPE); + + registerUsedImportsDuplicatedImportedElements(reg, lM2IPE); + + reg.registerAllUsedTypeNameToSpecifierTuples(importProvidedElements); + + // usages in script: + List externalDep = usedDependenciesTypeRefs(script); + + // mark used imports as seen in externalDep: + for (ScriptDependency scriptDep : externalDep) { + TModule mod = scriptDep.dependencyModule; + Pair> pM2IPE = findFirst(lM2IPE, p -> p.getKey() == mod); + if (pM2IPE != null) { + forEach(filter(pM2IPE.getValue(), ipe -> Objects.equals(ipe.getExportedName(), scriptDep.actualName) + && Objects.equals(ipe.getLocalName(), scriptDep.localName)), it -> it.markUsed()); + } + } + + /* + * TODO review ambiguous imports looks like reference to type that is ambiguously imported can happen only if + * there are errors in the import declaratiosn, so should ignore those references and resolve issues in the + * imports only? Or can this information be used to resolve those issues in smarter way? + */ + // localname2importprovider.markAmbigousImports(script) + + return reg; + } + + /** + * Registers conflicting or duplicate (based on imported elements) imports in the provided + * {@link RecordingImportState} + */ + private void registerUsedImportsDuplicatedImportedElements(RecordingImportState reg, + List>> module2imported) { + + for (Pair> pair : module2imported) { + List fromMod = pair.getValue(); + + // find duplicates in actual name, report them as duplicateImport + ArrayListMultimap name2Import = ArrayListMultimap.create(); + for (ImportProvidedElement ipe : fromMod) { + name2Import.put(ipe.getDuplicateImportUniqueName(), ipe); + } + + for (String name : name2Import.keySet()) { + List v = name2Import.get(name); + String actName = v.get(0).getExportedName(); + List x = toList(filter(v, internalIPE -> { + // filter out ImportProvidedElements that reflect Namespace element itself + ImportSpecifier specifier = internalIPE.getImportSpecifier(); + if (specifier instanceof NamespaceImportSpecifier) { + return !Objects.equals(internalIPE.getExportedName(), + computeNamespaceActualName((NamespaceImportSpecifier) specifier)); + } else { + return true; + } + })); + if (x.size() > 1) { + reg.registerDuplicateImportsOfSameElement(actName, pair.getKey(), x); + } + } + } + } + + /** + * Registers conflicting or duplicate (based on local name checks) imports in the provided + * {@link RecordingImportState} + */ + private void registerUsedImportsLocalNamesCollisions(RecordingImportState reg, + List>> localname2importprovider) { + for (Pair> pair : localname2importprovider) { + if (pair.getValue().size() > 1) { + reg.registerLocalNameCollision(pair.getKey(), pair.getValue()); + } + } + } + + /** + * analyzes provided {@link ImportDeclaration}s, if it finds *exact* duplicates, adds them to the + * {@link RecordingImportState#duplicatedImportDeclarations} + */ + private void registerDuplicatedImoprtDeclarationsFrom(RecordingImportState reg, + Iterable importDeclarations) { + Multimap nsisImports = LinkedHashMultimap.create(); + Multimap nisImports = LinkedHashMultimap.create(); + for (ImportDeclaration id : importDeclarations) { + if (findFirst(id.getImportSpecifiers(), is -> is instanceof NamespaceImportSpecifier) != null) { + nsisImports.put(id.getModule(), id); + } + if (findFirst(id.getImportSpecifiers(), is -> is instanceof NamedImportSpecifier) != null) { + nisImports.put(id.getModule(), id); + } + } + + for (TModule module : nsisImports.keySet()) { + registerImportDeclarationsWithNamespaceImportsForModule(nsisImports.get(module), reg); + } + + for (TModule module : nisImports.keySet()) { + registerImportDeclarationsWithNamedImportsForModule(nisImports.get(module), reg); + } + + // importDeclarations + // .filter[id| id.importSpecifiers.findFirst[is| is instanceof NamespaceImportSpecifier] !== null] + // .groupBy[module] + // .forEach[module, impDecls| + // registerImportDeclarationsWithNamespaceImportsForModule(impDecls, reg) + // ] + // + // for (ImportDeclaration id: importDeclarations ) { + // + // } + // importDeclarations + // .filter[id| id.importSpecifiers.findFirst[is| is instanceof NamedImportSpecifier] !== null] + // .groupBy[module] + // .forEach[module, impDecls| + // registerImportDeclarationsWithNamedImportsForModule(impDecls, reg) + // ] + } + + private void registerImportDeclarationsWithNamespaceImportsForModule( + Collection importDeclarations, + RecordingImportState reg) { + if (importDeclarations.size() < 2) { + return; + } + + List duplicates = new ArrayList<>(); + ImportDeclaration firstDeclaration = importDeclarations.iterator().next(); + String firstNamespaceName = head(filter(firstDeclaration.getImportSpecifiers(), NamespaceImportSpecifier.class)) + .getAlias(); + for (ImportDeclaration importDeclaration : tail(importDeclarations)) { + String followingNamespaceName = head( + filter(importDeclaration.getImportSpecifiers(), NamespaceImportSpecifier.class)).getAlias(); + if (Objects.equals(firstNamespaceName, followingNamespaceName)) { + duplicates.add(importDeclaration); + } + } + + if (!duplicates.isEmpty()) { + reg.registerDuplicatesOfImportDeclaration(firstDeclaration, duplicates); + } + } + + private void registerImportDeclarationsWithNamedImportsForModule(Collection importDeclarations, + RecordingImportState reg) { + if (importDeclarations.size() < 2) { + return; + } + + List duplicates = new ArrayList<>(); + ImportDeclaration firstDeclaration = importDeclarations.iterator().next(); + List firstDeclarationSpecifiers = toList( + filter(firstDeclaration.getImportSpecifiers(), NamedImportSpecifier.class)); + for (ImportDeclaration importDeclaration : tail(importDeclarations)) { + List followingDeclarationSpecifiers = toList( + filter(importDeclaration.getImportSpecifiers(), NamedImportSpecifier.class)); + if ((!firstDeclarationSpecifiers.isEmpty()) + && firstDeclarationSpecifiers.size() == followingDeclarationSpecifiers.size()) { + if (allFollowingMatchByNameAndAlias(firstDeclarationSpecifiers, followingDeclarationSpecifiers)) { + duplicates.add(importDeclaration); + } + } + } + + if (!duplicates.isEmpty()) { + reg.registerDuplicatesOfImportDeclaration(firstDeclaration, duplicates); + } + } + + private boolean allFollowingMatchByNameAndAlias(Iterable firstDeclarationSpecifiers, + Iterable followingDeclarationSpecifiers) { + + for (NamedImportSpecifier namedImportSpecifier : firstDeclarationSpecifiers) { + if (!exists(followingDeclarationSpecifiers, + otherNamedImportSpecifier -> Objects.equals(namedImportSpecifier.getAlias(), + otherNamedImportSpecifier.getAlias()) + && Objects.equals(namedImportSpecifier.getImportedElement().getName(), + otherNamedImportSpecifier.getImportedElement().getName()))) { + return false; + } + } + return true; + } + + /** + * Registers unused or broken (missing or unresolved imported module) import specifiers in the provided + * {@link RecordingImportState} + */ + private void registerUnusedAndBrokenImports(RecordingImportState reg, List importSpecifiers, + ASTMetaInfoCache astMetaInfoCache) { + for (ImportSpecifier is : importSpecifiers) { + // avoid duplicate error messages + if (!isNamedImportOfNonExportedElement(is, astMetaInfoCache) && !is.isFlaggedUsedInCode()) { + reg.registerUnusedImport(is); + if (isBrokenImport(is)) { + reg.registerBrokenImport(is); + } + } + } + } + + private static boolean isNamedImportOfNonExportedElement(ImportSpecifier importSpec, + ASTMetaInfoCache astMetaInfoCache) { + return astMetaInfoCache + .getLinkingIssueCodes(importSpec, N4JSPackage.Literals.NAMED_IMPORT_SPECIFIER__IMPORTED_ELEMENT) + .contains(IssueCodes.IMP_NOT_EXPORTED.name()); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportStateCalculator.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportStateCalculator.xtend deleted file mode 100644 index 85aa1e7a16..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportStateCalculator.xtend +++ /dev/null @@ -1,243 +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.tooling.organizeImports - -import com.google.common.collect.ArrayListMultimap -import com.google.inject.Inject -import java.util.List -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.ImportSpecifier -import org.eclipse.n4js.n4JS.N4JSPackage -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.postprocessing.ASTMetaInfoCache -import org.eclipse.n4js.resource.N4JSResource -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.utils.Log -import org.eclipse.n4js.validation.IssueCodes -import org.eclipse.n4js.validation.JavaScriptVariantHelper - -import static extension org.eclipse.n4js.tooling.organizeImports.ImportSpecifiersUtil.* -import static extension org.eclipse.n4js.tooling.organizeImports.ScriptDependencyResolver.* - -/** - * Analyzes all imports in a script. Builds up a data structure of {@link RecordingImportState} to capture the findings. - */ -@Log -class ImportStateCalculator { - - @Inject - private JavaScriptVariantHelper jsVariantHelper; - - /** - * Algorithm to check the Model for Issues with Imports. - * @returns {@link RecordingImportState} - */ - public def RecordingImportState calculateImportstate(Script script) { - val reg = new RecordingImportState(); - - val astMetaInfoCache = (script.eResource as N4JSResource).getASTMetaInfoCacheVerifyContext(); - - // Calculate Available - val importDeclarationsALL = script.scriptElements.filter(ImportDeclaration) - - reg.registerDuplicatedImoprtDeclarationsFrom(importDeclarationsALL) - - val importSpecifiersUnAnalyzed = importDeclarationsALL.filter[!reg.isDuplicatingImportDeclaration(it)].map[importSpecifiers].flatten.toList - -// markDuplicatingSpecifiersAsUnused(importSpecifiersUnAnalyzed) - - // collect all unused if stable - reg.registerUnusedAndBrokenImports(importSpecifiersUnAnalyzed, astMetaInfoCache) - - val List importProvidedElements = importSpecifiersUnAnalyzed.filter[!(reg.brokenImports.contains(it))].toList.mapToImportProvidedElements(jsVariantHelper) - - //refactor into specific types, those are essentially Maps holding elements in insertion order (keys and values) - val List>> lN2IPE = newArrayList() - val List>> lM2IPE = newArrayList() - - //TODO refactor this, those composed collections should be encapsulated as specific types with proper get/set methods - for (ipe : importProvidedElements) { - val pN2IPE = lN2IPE.findFirst[it.key == ipe.getCollisionUniqueName()]; - if(pN2IPE !== null){ - pN2IPE.value.add(ipe) - }else{ - lN2IPE.add(ipe.getCollisionUniqueName() -> newArrayList(ipe)) - } - val pM2IPE = lM2IPE.findFirst[it.key == ipe.importedModule]; - if(pM2IPE !== null){ - pM2IPE.value.add(ipe) - }else{ - lM2IPE.add(ipe.importedModule -> newArrayList(ipe)) - } - } - - reg.registerUsedImportsLocalNamesCollisions(lN2IPE) - - reg.registerUsedImportsDuplicatedImportedElements(lM2IPE) - - reg.registerAllUsedTypeNameToSpecifierTuples(importProvidedElements) - - - // usages in script: - val externalDep = script.usedDependenciesTypeRefs - - // mark used imports as seen in externalDep: - for( scriptDep : externalDep ) { - val mod = scriptDep.dependencyModule - val pM2IPE = lM2IPE.findFirst[it.key == mod] - if(pM2IPE !== null){ - pM2IPE.value.filter[it.exportedName == scriptDep.actualName && it.getLocalName() == scriptDep.localName ].forEach[ it.markUsed]; - } - } - -/*TODO review ambiguous imports - * looks like reference to type that is ambiguously imported can happen only if there are errors in the import declaratiosn, - * so should ignore those references and resolve issues in the imports only? - * Or can this information be used to resolve those issues in smarter way? - */ -// localname2importprovider.markAmbigousImports(script) - - - return reg; - } - - /** - * Registers conflicting or duplicate (based on imported elements) imports in the provided {@link RecordingImportState} - */ - private def registerUsedImportsDuplicatedImportedElements(RecordingImportState reg, List>> module2imported) { - for (pair : module2imported) { - val fromMod = pair.value - - // find duplicates in actual name, report them as duplicateImport - val name2Import = ArrayListMultimap.create - for (ipe : fromMod) - name2Import.put(ipe.getDuplicateImportUniqueName, ipe) - - for (name : name2Import.keySet) { - val v = name2Import.get(name).toList - val actName = v.head.exportedName; - val x = v.filter[internalIPE| - // filter out ImportProvidedElements that reflect Namespace element itself - val specifier = internalIPE.importSpecifier; - if(specifier instanceof NamespaceImportSpecifier){ - internalIPE.exportedName != computeNamespaceActualName(specifier) - }else{ - true - } - ].toList - if (x.size > 1) - reg.registerDuplicateImportsOfSameElement(actName, pair.key, x) - } - } - } - - /** - * Registers conflicting or duplicate (based on local name checks) imports in the provided {@link RecordingImportState} - */ - private def registerUsedImportsLocalNamesCollisions(RecordingImportState reg, List>> localname2importprovider) { - for(pair : localname2importprovider){ - if(pair.value.size>1){ - reg.registerLocalNameCollision(pair.key, pair.value) - } - } - } - - /** - * analyzes provided {@link ImportDeclaration}s, if it finds *exact* duplicates, adds them to the {@link RecordingImportState#duplicatedImportDeclarations} - */ - private def void registerDuplicatedImoprtDeclarationsFrom(RecordingImportState reg, Iterable importDeclarations) { - - importDeclarations - .filter[id| id.importSpecifiers.findFirst[is| is instanceof NamespaceImportSpecifier] !== null] - .groupBy[module] - .forEach[module, impDecls| - registerImportDeclarationsWithNamespaceImportsForModule(impDecls, reg) - ] - - importDeclarations - .filter[id| id.importSpecifiers.findFirst[is| is instanceof NamedImportSpecifier] !== null] - .groupBy[module] - .forEach[module, impDecls| - registerImportDeclarationsWithNamedImportsForModule(impDecls, reg) - ] - } - - private def void registerImportDeclarationsWithNamespaceImportsForModule(List importDeclarations, - RecordingImportState reg) { - if (importDeclarations.size < 2) - return; - - val duplicates = newArrayList - val firstDeclaration = importDeclarations.head - val firstNamespaceName = (firstDeclaration.importSpecifiers.filter(NamespaceImportSpecifier).head).alias - importDeclarations.tail.forEach [ importDeclaration | - val followingNamespaceName = importDeclaration.importSpecifiers.filter(NamespaceImportSpecifier).head.alias - if (firstNamespaceName == followingNamespaceName) - duplicates.add(importDeclaration) - ] - - if (!duplicates.empty) - reg.registerDuplicatesOfImportDeclaration(firstDeclaration, duplicates); - } - - private def void registerImportDeclarationsWithNamedImportsForModule(List importDeclarations, - RecordingImportState reg) { - if (importDeclarations.size < 2) - return; - - val duplicates = newArrayList - val firstDeclaration = importDeclarations.head - val firstDeclarationSpecifiers = firstDeclaration.importSpecifiers.filter(NamedImportSpecifier) - importDeclarations.tail.forEach [ importDeclaration | - val followingDeclarationSpecifiers = importDeclaration.importSpecifiers.filter(NamedImportSpecifier) - if ((!firstDeclarationSpecifiers.empty) && - firstDeclarationSpecifiers.size === followingDeclarationSpecifiers.size) { - if (firstDeclarationSpecifiers.allFollowingMatchByNameAndAlias(followingDeclarationSpecifiers)) - duplicates.add(importDeclaration) - } - ] - - if (!duplicates.empty) - reg.registerDuplicatesOfImportDeclaration(firstDeclaration, duplicates); - } - - private def boolean allFollowingMatchByNameAndAlias(Iterable firstDeclarationSpecifiers, - Iterable followingDeclarationSpecifiers) { - - firstDeclarationSpecifiers.forall [ namedImportSpecifier | - followingDeclarationSpecifiers.exists [ otherNamedImportSpecifier | - namedImportSpecifier.alias == otherNamedImportSpecifier.alias && - namedImportSpecifier.importedElement.name == otherNamedImportSpecifier.importedElement.name - ] - ] - } - - /** - * Registers unused or broken (missing or unresolved imported module) import specifiers in the provided {@link RecordingImportState} - */ - private def void registerUnusedAndBrokenImports(RecordingImportState reg, List importSpecifiers, ASTMetaInfoCache astMetaInfoCache) { - for (is : importSpecifiers) { - if (!isNamedImportOfNonExportedElement(is, astMetaInfoCache) // avoid duplicate error messages - && !is.isFlaggedUsedInCode) { - reg.registerUnusedImport(is); - if (is.isBrokenImport) - reg.registerBrokenImport(is); - } - } - } - - private static def boolean isNamedImportOfNonExportedElement(ImportSpecifier importSpec, ASTMetaInfoCache astMetaInfoCache) { - return astMetaInfoCache.getLinkingIssueCodes(importSpec, N4JSPackage.Literals.NAMED_IMPORT_SPECIFIER__IMPORTED_ELEMENT) - .contains(IssueCodes.IMP_NOT_EXPORTED.name()); - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/InjectedTypesResolverUtility.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/InjectedTypesResolverUtility.java new file mode 100644 index 0000000000..45c5e46f3a --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/InjectedTypesResolverUtility.java @@ -0,0 +1,146 @@ +/** + * 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.tooling.organizeImports; + +import static org.eclipse.n4js.AnnotationDefinition.BIND; +import static org.eclipse.n4js.AnnotationDefinition.BINDER; +import static org.eclipse.n4js.AnnotationDefinition.GENERATE_INJECTOR; +import static org.eclipse.n4js.AnnotationDefinition.INJECT; +import static org.eclipse.n4js.AnnotationDefinition.PROVIDES; +import static org.eclipse.n4js.AnnotationDefinition.USE_BINDER; +import static org.eclipse.n4js.AnnotationDefinition.WITH_PARENT_INJECTOR; +import static org.eclipse.n4js.tooling.organizeImports.DIUtility.getProvidedType; +import static org.eclipse.n4js.tooling.organizeImports.DIUtility.isProviderType; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filterNull; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.last; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.toIterable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.n4js.n4JS.Annotation; +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.n4JS.N4ClassifierDefinition; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4MethodDeclaration; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.n4JS.TypeRefAnnotationArgument; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TFormalParameter; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.ts.types.Type; + +/** + * Utility for computing dependencies for N4JS DI mechanisms. + */ +public class InjectedTypesResolverUtility { + + /** + * Search through {@link Script} to find all {@link N4ClassifierDefinition}s. From found entries builds collection + * of {@link Type}s used with DI annotations. + */ + public static List findAllInjected(Script script) { + List injections = new ArrayList<>(); + + for (N4ClassifierDefinition cl : toIterable(filter(script.eAllContents(), N4ClassifierDefinition.class))) { + + // If has owned injected ctor with formal params, then use params types + for (TFormalParameter fp : getOwnedInjectedCtorParams(cl)) { + injections.add(getDeclaredTypeFromTypeRef(fp.getTypeRef())); + } + + // Injected fields. + for (N4FieldDeclaration fd : cl.getOwnedFields()) { + if (INJECT.hasAnnotation(fd)) { + injections.add(getDeclaredTypeFromTypeRef(fd.getDeclaredTypeRef())); + } + } + + // Binder specific + if (BINDER.hasOwnedAnnotation(cl)) { + // Binder's @Bind dependencies. + for (Annotation an : BIND.getAllOwnedAnnotations(cl)) { + // Since bind has only two arguments, both are TypeRefs. + injections.add(getDeclaredTypeFromTypeRef((TypeRef) an.getArgs().get(0).value())); + injections.add(getDeclaredTypeFromTypeRef((TypeRef) last(an.getArgs()).value())); + } + + // Binder's @Provides dependencies. + for (N4MethodDeclaration m : cl.getOwnedMethods()) { + if (PROVIDES.hasAnnotation(m)) { + injections.add(getDeclaredTypeFromTypeRef(m.getDeclaredReturnTypeRef())); + + for (FormalParameter fp : m.getFpars()) { + injections.add(getDeclaredTypeFromTypeRef(fp.getDeclaredTypeRef())); + } + } + } + } + + // DIComponent specific + if (GENERATE_INJECTOR.hasOwnedAnnotation(cl)) { + if (WITH_PARENT_INJECTOR.hasOwnedAnnotation(cl)) { + var ann = WITH_PARENT_INJECTOR.getOwnedAnnotation(cl); + if (ann != null) { + injections + .add(((TypeRefAnnotationArgument) ann.getArgs().get(0)).getTypeRef().getDeclaredType()); + } + } + + if (USE_BINDER.hasOwnedAnnotation(cl)) { + Iterable anns = USE_BINDER.getAllOwnedAnnotations(cl); + if (anns != null) { + var argsTypes = map( + filterNull(map(anns, a -> ((TypeRefAnnotationArgument) a.getArgs().get(0)))), + arg -> arg.getTypeRef().getDeclaredType()); + + for (Type at : argsTypes) { + injections.add(at); + } + } + } + } + } + return toList(filterNull(injections)); + } + + private static List getOwnedInjectedCtorParams(N4ClassifierDefinition cd) { + if (cd != null && cd.getDefinedType() instanceof TClass) { + TMethod ctor = ((TClass) cd.getDefinedType()).getOwnedCtor(); + if (null != ctor) { + if (INJECT.hasAnnotation(ctor)) + return ctor.getFpars(); + } + } + return Collections.emptyList(); + } + + /** + * resolves declared type from type ref. If declared type is N4Provider (can be nested) resolves to (nested) + * provided type. + */ + private static Type getDeclaredTypeFromTypeRef(TypeRef typeRef) { + if (isProviderType(typeRef)) { + Type providedType = getProvidedType(typeRef); + if (null != providedType) { + return providedType; + } + } else { + return typeRef.getDeclaredType(); + } + return null; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/InjectedTypesResolverUtility.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/InjectedTypesResolverUtility.xtend deleted file mode 100644 index 974eb29826..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/InjectedTypesResolverUtility.xtend +++ /dev/null @@ -1,114 +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.tooling.organizeImports - -import java.util.Collection -import org.eclipse.n4js.n4JS.N4ClassifierDefinition -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.n4JS.TypeRefAnnotationArgument -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.TClass -import org.eclipse.n4js.ts.types.Type - -import static org.eclipse.n4js.AnnotationDefinition.* - -import static extension org.eclipse.n4js.tooling.organizeImports.DIUtility.* - -/** - * Utility for computing dependencies for N4JS DI mechanisms. - */ -class InjectedTypesResolverUtility { - - /** - * Search through {@link Script} to find all {@link N4ClassifierDefinition}s. - * From found entries builds collection of {@link Type}s used with DI annotations. - */ - public static def Collection findAllInjected(Script script) { - val injections = newArrayList; - - script.eAllContents.filter(N4ClassifierDefinition).forEach [ cl | - - // If has owned injected ctor with formal params, then use params types - cl.getOwnedInjectedCtorParams.forEach[ - injections.add(getDeclaredTypeFromTypeRef(it.typeRef)) - ]; - // Injected fields. - cl.ownedFields.forEach [ f | - if (INJECT.hasAnnotation(f)) { - injections.add(getDeclaredTypeFromTypeRef(f.declaredTypeRef)); - } - ] - - //Binder specific - if(BINDER.hasOwnedAnnotation(cl)){ - // Binder's @Bind dependencies. - BIND.getAllOwnedAnnotations(cl).forEach [ an | - // Since bind has only two arguments, both are TypeRefs. - injections.add(getDeclaredTypeFromTypeRef(an.args.head.value as TypeRef)); - injections.add(getDeclaredTypeFromTypeRef(an.args.last.value as TypeRef)); - ] - - // Binder's @Provides dependencies. - cl.ownedMethods.forEach [ m | - if (PROVIDES.hasAnnotation(m)) { - injections.add(getDeclaredTypeFromTypeRef(m.declaredReturnTypeRef)); - m.fpars.forEach[injections.add(getDeclaredTypeFromTypeRef(it.declaredTypeRef))]; - } - ]; - } - - //DIComponent specific - if(GENERATE_INJECTOR.hasOwnedAnnotation(cl)){ - if(WITH_PARENT_INJECTOR.hasOwnedAnnotation(cl)){ - var ann = WITH_PARENT_INJECTOR.getOwnedAnnotation(cl) - if(ann !== null){ - injections.add((ann.args.head as TypeRefAnnotationArgument).typeRef.declaredType) - } - } - - if(USE_BINDER.hasOwnedAnnotation(cl)){ - var anns = USE_BINDER.getAllOwnedAnnotations(cl) - if(anns !== null){ - var argsTypes = anns.map[(args.head as TypeRefAnnotationArgument)].filterNull.map[typeRef.declaredType]; - argsTypes.forEach[injections.add(it)]; - } - } - } - - ]; - return injections.filterNull.toList; - } - - private static def getOwnedInjectedCtorParams(N4ClassifierDefinition it) { - if (it?.definedType instanceof TClass) { - val ctor = (definedType as TClass).ownedCtor; - if (null !== ctor) { - if (INJECT.hasAnnotation(ctor)) return ctor.fpars; - } - } - return emptyList; - } - - /** - * resolves declared type from type ref. If declared type is - * N4Provider (can be nested) resolves to (nested) provided type. - */ - private static def getDeclaredTypeFromTypeRef(TypeRef typeRef) { - if (typeRef.providerType) { - val providedType = typeRef.providedType; - if (null !== providedType) { - return providedType; - } - } else { - typeRef.declaredType; - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/RecordingImportState.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/RecordingImportState.java new file mode 100644 index 0000000000..765c95191b --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/RecordingImportState.java @@ -0,0 +1,176 @@ +/** + * 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.tooling.organizeImports; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.emf.common.notify.Adapter; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.xtext.xbase.lib.Pair; + +/** + * Register holding results of analyzing {@link ImportDeclaration}s and contained {@link ImportSpecifier}s. + */ +@SuppressWarnings("javadoc") +public class RecordingImportState { + + public List>> duplicatedImportDeclarations = new ArrayList<>(); + + public List unusedImports = new ArrayList<>(); + public List brokenImports = new ArrayList<>(); + + /* TODO refactor nested collections into specialized types */ + public List, List>> duplicateImportsOfSameElement = new ArrayList<>(); + public List>> localNameCollision = new ArrayList<>(); + + public List allUsedTypeNameToSpecifierTuples = new ArrayList<>(); + + public void registerDuplicatesOfImportDeclaration(ImportDeclaration declaration, + List duplicates) { + duplicatedImportDeclarations.add(Pair.of(declaration, duplicates)); + } + + public boolean isDuplicatingImportDeclaration(ImportDeclaration importDeclaration) { + return exists(duplicatedImportDeclarations, + dupePair -> dupePair.getKey().getModule() == importDeclaration.getModule() && + dupePair.getValue().contains(importDeclaration)); + } + + public void registerUnusedImport(ImportSpecifier specifier) { + unusedImports.add(specifier); + } + + public void registerBrokenImport(ImportSpecifier specifier) { + brokenImports.add(specifier); + } + + /** + * Removes any information stored in the register. + */ + public void cleanup() { + // clean own + unusedImports.clear(); + brokenImports.clear(); + allUsedTypeNameToSpecifierTuples.clear(); + + localNameCollision.clear(); + duplicatedImportDeclarations.clear(); + duplicateImportsOfSameElement.clear(); + + // cut ref: + allUsedTypeNameToSpecifierTuples = null; + } + + public void registerAllUsedTypeNameToSpecifierTuples(List pairs) { + allUsedTypeNameToSpecifierTuples = pairs; + } + + // remove all known unused imports from the passed in list. + public void removeDuplicatedImportsOfSameelement(List declarations, Adapter nodelessMarker) { + for (Pair, List> e : duplicateImportsOfSameElement) { + for (ImportProvidedElement e2 : e.getValue()) { + List l = new ArrayList<>(); + l.add(e2.getImportSpecifier()); + removeFrom(l, declarations, nodelessMarker); + } + } + } + + public void removeLocalNameCollisions(List declarations, Adapter nodelessMarker) { + for (Pair> e : localNameCollision) { + for (ImportProvidedElement e2 : e.getValue()) { + List l = new ArrayList<>(); + l.add(e2.getImportSpecifier()); + removeFrom(l, declarations, nodelessMarker); + } + } + } + + public void removeDuplicatedImportDeclarations(List declarations) { + for (Pair> decl2dupes : duplicatedImportDeclarations) { + declarations.removeAll(decl2dupes.getValue()); + } + } + + public void removeUnusedImports(List declarations, Adapter nodelessMarker) { + removeFrom(unusedImports, declarations, nodelessMarker); + } + + public void removeBrokenImports(List declarations, Adapter nodelessMarker) { + removeFrom(brokenImports, declarations, nodelessMarker); + } + + private void removeFrom(List tobeRemoved, List declarations, + Adapter nodelessMarker) { + + // remove from declarations + for (ImportSpecifier it : tobeRemoved) { + if (it instanceof NamespaceImportSpecifier) { + declarations.remove(it.eContainer()); + + } else if (it instanceof NamedImportSpecifier) { + ImportDeclaration imp = (ImportDeclaration) it.eContainer(); + if (imp != null) { + if (imp.getImportSpecifiers() != null) { + imp.getImportSpecifiers().remove(it); + // mark as modified to rebuild whole node. + imp.eAdapters().add(nodelessMarker); + } + if (imp.getImportSpecifiers() == null || imp.getImportSpecifiers().isEmpty()) { + declarations.remove(imp); + } + } + } + } + } + + /** + * Returns set of names, for which there are broken import specifiers. Happens after broken imports are removed, + * which makes name (usually IdRef/TypeRef) refer to ImportSpecifier, which is being removed from document. + * Providing list of broken names, allows organize imports to fix those. + */ + public Set calcRemovedImportedNames() { + Set ret = new HashSet<>(); + for (ImportProvidedElement e : allUsedTypeNameToSpecifierTuples) { + if (e.isUsed() && e.getImportSpecifier().eContainer() == null) { + ret.add(e.getLocalName()); + } + } + return ret; + } + + public void registerDuplicateImportsOfSameElement(String name, TModule module, + List elements) { + duplicateImportsOfSameElement.add(Pair.of(Pair.of(name, module), elements)); + } + + public void registerLocalNameCollision(String string, List elements) { + Pair> key = findFirst(localNameCollision, + it -> Objects.equals(it.getKey(), string)); + if (key != null) { + key.getValue().addAll(elements); + } else { + localNameCollision.add(Pair.of(string, elements)); + } + } + +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/RecordingImportState.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/RecordingImportState.xtend deleted file mode 100644 index 7a5334426a..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/RecordingImportState.xtend +++ /dev/null @@ -1,161 +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.tooling.organizeImports - -import java.util.List -import java.util.Set -import org.eclipse.emf.common.notify.Adapter -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.ImportSpecifier -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.ts.types.TModule - -/** - * Register holding results of analyzing {@link ImportDeclaration}s - * and contained {@link ImportSpecifier}s. - */ -class RecordingImportState { - - public List>> duplicatedImportDeclarations = newArrayList(); - - public List unusedImports = newArrayList(); - public List brokenImports = newArrayList(); - - /*TODO refactor nested collections into specialized types */ - public List, List>> duplicateImportsOfSameElement = newArrayList(); - public List>> localNameCollision = newArrayList(); - - public List allUsedTypeNameToSpecifierTuples = newArrayList(); - - def registerDuplicatesOfImportDeclaration(ImportDeclaration declaration, List duplicates) { - duplicatedImportDeclarations.add(declaration -> duplicates); - } - - def boolean isDuplicatingImportDeclaration(ImportDeclaration importDeclaration){ - duplicatedImportDeclarations.exists[dupePair| - dupePair.key.module === importDeclaration.module && - dupePair.value.contains(importDeclaration) - ] - } - - def registerUnusedImport(ImportSpecifier specifier) { - unusedImports.add(specifier); - } - - def registerBrokenImport(ImportSpecifier specifier) { - brokenImports.add(specifier); - } - - /** - * Removes any information stored in the register. - */ - def cleanup() { - - // clean own - #[unusedImports, brokenImports, allUsedTypeNameToSpecifierTuples].forEach[clear] - - localNameCollision.clear - duplicatedImportDeclarations.clear - duplicateImportsOfSameElement.clear - - // cut ref: - allUsedTypeNameToSpecifierTuples = null; - } - - def registerAllUsedTypeNameToSpecifierTuples(List pairs) { - allUsedTypeNameToSpecifierTuples = pairs - } - - // remove all known unused imports from the passed in list. - def removeDuplicatedImportsOfSameelement(List declarations, Adapter nodelessMarker ) { - duplicateImportsOfSameElement.forEach[e| - e.value.forEach[e2| - newArrayList(e2.importSpecifier).removeFrom(declarations, nodelessMarker) - ] - ] - } - - def removeLocalNameCollisions(List declarations, Adapter nodelessMarker ) { - localNameCollision.forEach[e| - e.value.forEach[e2| - newArrayList(e2.importSpecifier).removeFrom(declarations, nodelessMarker) - ] - ] - return - } - - def removeDuplicatedImportDeclarations(List declarations) { - duplicatedImportDeclarations.forEach[decl2dupes| - declarations.removeAll(decl2dupes.value) - ] - } - - def removeUnusedImports(List declarations, Adapter nodelessMarker ) { - unusedImports.removeFrom(declarations, nodelessMarker) - } - - def removeBrokenImports(List declarations, Adapter nodelessMarker ) { - brokenImports.removeFrom(declarations, nodelessMarker ) - } - - private def removeFrom(List tobeRemoved, List declarations, Adapter nodelessMarker ) { - //remove from declarations - tobeRemoved.forEach [ - switch (it) { - NamespaceImportSpecifier: - declarations.remove(it.eContainer) - NamedImportSpecifier: { - val imp = it.eContainer as ImportDeclaration - if (imp !== null) { - if (imp.importSpecifiers !== null) { - imp.importSpecifiers.remove(it); - // mark as modified to rebuild whole node. - imp.eAdapters.add(nodelessMarker) - } - if (imp.importSpecifiers === null || imp.importSpecifiers.empty) { - declarations.remove(imp) - } - } - } - } - ] - } - - /** - * Returns set of names, for which there are broken import specifiers. Happens after broken - * imports are removed, which makes name (usually IdRef/TypeRef) refer to ImportSpecifier, which - * is being removed from document. Providing list of broken names, allows organize imports to fix those. - */ - def Set calcRemovedImportedNames() { - var ret = newHashSet() - for (e : allUsedTypeNameToSpecifierTuples) { - if (e.used && e.importSpecifier.eContainer === null) { - ret.add(e.getLocalName()) - } - } - return ret - } - - def registerDuplicateImportsOfSameElement(String name, TModule module, List elements) { - duplicateImportsOfSameElement.add((name->module)->elements); - } - - def registerLocalNameCollision(String string, List elements) { - val key = localNameCollision.findFirst[it.key == string]; - if(key !== null){ - key.value.addAll(elements) - }else{ - localNameCollision.add(string -> elements.toList) - } - } - -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/RefNameUtil.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/RefNameUtil.java new file mode 100644 index 0000000000..5c47b39bfd --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/RefNameUtil.java @@ -0,0 +1,76 @@ +/** + * 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.tooling.organizeImports; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.join; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; + +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.types.TypingStrategy; +import org.eclipse.xtext.nodemodel.ICompositeNode; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; + +/** + * Utility to find actual name that was used for given reference. + */ +class RefNameUtil { + + /** + * Finds name that is used as identifier. + */ + public static String findIdentifierName(IdentifierRef ref) { + return join(map(filter(NodeModelUtils.findActualNodeFor(ref).getLeafNodes(), n -> !n.isHidden()), + n -> n.getText())); + } + + /** + * Finds the name in the ParameterizedTypeRef. + * + * @return null if no connection to AST + */ + public static String findTypeName(ParameterizedTypeRef ref) { + ICompositeNode astNode = NodeModelUtils.findActualNodeFor(ref); + if (astNode != null) { + int prefixLen = 0; + int suffixLen = 0; + String nodeText = join(map(filter(astNode.getLeafNodes(), n -> !n.isHidden()), n -> n.getText())); + + if (!ref.getDefinedTypingStrategy().equals(TypingStrategy.NOMINAL)) { + String typingLiteral = ref.getDefinedTypingStrategy().getLiteral(); + if (nodeText.startsWith(typingLiteral)) { + // handle things like + // foo2 : ~r~ /* ~r~ */ A + // nodeText does not contain whitespace or comments, so it is like + // ~r~A + // drop typing strategy literal value and return just + // A + prefixLen = ref.getDefinedTypingStrategy().getLiteral().length(); + } + } + + // handle A? + if (ref.isFollowedByQuestionMark() && nodeText.endsWith("?")) { + suffixLen = 1; + } + + // handle A+ + if (ref.isDynamic() && nodeText.endsWith("+")) { + suffixLen = 1; + } + + return nodeText.substring(prefixLen, nodeText.length() - suffixLen); + } else { + return null; + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/RefNameUtil.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/RefNameUtil.xtend deleted file mode 100644 index 0d7de932a5..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/RefNameUtil.xtend +++ /dev/null @@ -1,68 +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.tooling.organizeImports - -import org.eclipse.n4js.n4JS.IdentifierRef -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.types.TypingStrategy -import org.eclipse.xtext.nodemodel.util.NodeModelUtils - -/** - * Utility to find actual name that was used for given reference. - */ -class RefNameUtil { - /** - * Finds name that is used as identifier. - */ - def public static String findIdentifierName(IdentifierRef ref) { - NodeModelUtils.findActualNodeFor(ref).leafNodes.filter[!hidden].map[text].join - } - - /** - * Finds the name in the ParameterizedTypeRef. - * @return null if no connection to AST - */ - def public static String findTypeName(ParameterizedTypeRef ref) { - val astNode = NodeModelUtils.findActualNodeFor(ref) - if (astNode !== null) { - var prefixLen = 0 - var suffixLen = 0 - val nodeText = astNode.leafNodes.filter[!hidden].map[text].join - - if(!ref.definedTypingStrategy.equals(TypingStrategy.NOMINAL)){ - val typingLiteral = ref.definedTypingStrategy.literal - if(nodeText.startsWith(typingLiteral)){ - // handle things like - // foo2 : ~r~ /* ~r~ */ A - // nodeText does not contain whitespace or comments, so it is like - // ~r~A - // drop typing strategy literal value and return just - // A - prefixLen = ref.definedTypingStrategy.literal.length - } - } - - // handle A? - if(ref.isFollowedByQuestionMark && nodeText.endsWith('?')) { - suffixLen = 1; - } - - //handle A+ - if(ref.dynamic && nodeText.endsWith('+')) { - suffixLen = 1; - } - - return nodeText.substring(prefixLen, nodeText.length - suffixLen) - } else { - null - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ScriptDependencyResolver.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ScriptDependencyResolver.java new file mode 100644 index 0000000000..100e9c0f06 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ScriptDependencyResolver.java @@ -0,0 +1,423 @@ +/** + * 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.tooling.organizeImports; + +import static org.eclipse.n4js.tooling.organizeImports.InjectedTypesResolverUtility.findAllInjected; +import static org.eclipse.n4js.tooling.organizeImports.RefNameUtil.findTypeName; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filterNull; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.isNullOrEmpty; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toMap; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.toInvertedMap; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.toList; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Function; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.n4JS.AnnotableElement; +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.N4InterfaceDeclaration; +import org.eclipse.n4js.n4JS.N4TypeDeclaration; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType; +import org.eclipse.n4js.ts.types.TAnnotableElement; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TDynamicElement; +import org.eclipse.n4js.ts.types.TExportableElement; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.TInterface; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.ts.types.TVariable; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.utils.ResourceType; +import org.eclipse.xtext.EcoreUtil2; + +/** + * Static analysis for {@link Script} dependencies. Analyzes all identifiers in the {@link Script}, and on (on demand) + * {@link Type}s or {@link TypeRef}s. During analysis identifiers from {@link NamedImportSpecifier} and + * {@link NamespaceImportSpecifier} are taken into account. Note that if result analysis does not contain descriptions + * matching some of the imports, it means those imports are not used, and can be removed from script, or ignored during + * compilation. + * + * Result of analysis is list of {@link ScriptDependency} instances describing what {@link EObject}s should be imported. + *
    + * Note: + *
  • declarations marked with "@ProvidedByRuntime" are ignored (they are never in the result)
  • + *
  • declarations marked with "@Global" are not ignored (can show up in the result)
  • + *
+ */ +@SuppressWarnings("unused") +public class ScriptDependencyResolver { + + /** + * Resolves dependencies only from {@link IdentifierRef}s, no {@link Type}s or {@link TypeRef}s are taken into + * account. + */ + public static List usedDependencies(Script script) { + if (null == script) { + return Collections.emptyList(); + } + return allRequiredExternalDeclaration(script, Collections.emptySet(), Collections.emptySet()); + } + + /** + * Resolves dependencies from {@link IdentifierRef}s and {@link Type}s that are injected into local type + * declarations. + */ + public static List usedDependenciesWithInejctedTypes(Script script) { + if (null == script) { + return Collections.emptyList(); + } + return allRequiredExternalDeclaration(script, findAllInjected(script), Collections.emptySet()); + } + + /** + * Resolves dependencies from {@link IdentifierRef}s and all {@link TypeRef}s. + */ + public static List usedDependenciesTypeRefs(Script script) { + if (null == script) { + return Collections.emptyList(); + } + return allRequiredExternalDeclaration(script, Collections.emptySet(), + toList(filter(script.eAllContents(), TypeRef.class))); + } + + /** + * Looks through all {@link IdentifierRef} for external dependencies (from different module than currently analyzed + * script containing module). Additionally looks through all types used as super types and implemented interfaces. + * Not used types (see {@link #shouldBeImported}) are removed from external dependencies. + * + * @param script + * to be analyzed + * @param typesToBeIncluded + * force specific collection of {@link Type}s to be considered for as dependencies + * @param typeRefsToBeIncluded + * force specific collection of {@link TypeRef}s to be considered for as dependencies + */ + private static List allRequiredExternalDeclaration(Script script, + Collection typesToBeIncluded, + Collection typeRefsToBeIncluded) { + + List indirectlyImported = toList(filter(script.eAllContents(), N4TypeDeclaration.class)); + List identifierRefs = toList(filter(script.eAllContents(), IdentifierRef.class)); + + List potentialDependencies = new ArrayList<>(); + potentialDependencies.addAll(indirectlyImported); + potentialDependencies.addAll(identifierRefs); + potentialDependencies.addAll(typesToBeIncluded); + potentialDependencies.addAll(typeRefsToBeIncluded); + + List namedImportSpecifiers = toList( + filter(filter(script.eAllContents(), NamedImportSpecifier.class), + nis -> nis.getImportedElement() != null)); + Map usedNamespaceSpecifiers = toInvertedMap( + filter(script.eAllContents(), NamespaceImportSpecifier.class), x -> false); + + TModule baseModule = script.getModule(); + + Set sortedDeps = new TreeSet<>(Comparator.comparing(dep -> dep.localName)); + for (EObject eo : potentialDependencies) { + Map nisByName = toMap(namedImportSpecifiers, + nis -> nis.getImportedElement().getName()); + + Iterable deps = handle(eo, nisByName, usedNamespaceSpecifiers, + eo2 -> shouldBeImported(baseModule, eo2)); + + for (ScriptDependency dep : deps) { + if (dep != null) { + sortedDeps.add(dep); + } + } + } + + return new ArrayList<>(sortedDeps); + } + + /** + * Checks if a given EObject should be imported. + * + *
    + * Evaluates to true if: + *
  • provided EO is not from the module provided at creation time (that module is assumed to be one for which we + * analyze dependencies)
  • + *
  • provided EO is not from built in types
  • + *
  • (in case of AST elements) is not annotated with {@link AnnotationDefinition#PROVIDED_BY_RUNTIME}
  • + *
  • (in case of TS elements) providedByRuntime evaluates to false
  • + *
+ * + * @returns true if given EO should be imported + */ + public static boolean shouldBeImported(TModule baseModule, EObject eo) { + if (eo instanceof ModuleNamespaceVirtualType + || eo instanceof TDynamicElement) { + return true; + } + + EObject containingModule = EcoreUtil.getRootContainer(eo); + if (containingModule instanceof TModule) { + if (AnnotationDefinition.PROVIDED_BY_RUNTIME.hasAnnotation((TModule) containingModule) + || (ResourceType.getResourceType(containingModule) == ResourceType.DTS + && AnnotationDefinition.GLOBAL.hasAnnotation((TModule) containingModule))) { + return false; + } + } + + if (eo instanceof AnnotableElement) { + if (AnnotationDefinition.PROVIDED_BY_RUNTIME.hasAnnotation((AnnotableElement) eo)) { + return false; + } + } else if (eo instanceof TAnnotableElement) { + if (AnnotationDefinition.PROVIDED_BY_RUNTIME.hasAnnotation((TAnnotableElement) eo)) { + return false; + } else if (eo instanceof Type) { + // TODO is this dead code + if (((Type) eo).isProvidedByRuntime()) { + return false; + } + } else if (eo instanceof TVariable) { + // TODO is this dead code + if (((TVariable) eo).isProvidedByRuntime()) { + return false; + } + } + } + + // ignore built-in things as n4scheme:/console.n4jsd: + // in non platform realm check if URI describes file, + // in eclipse platform realm check if URI describes platform resource + Resource res = eo.eResource(); + return res != null && res.getURI() != null // + && ((res.getURI().isFile() || res.getURI().isPlatformResource())) // + // and check if modules/files are different + && (!res.getURI().toString().equals(baseModule.eResource().getURI().toString())); + } + + private static boolean isNamespaceDependencyHandlingNeeded( + Map usedNamespaceSpecifiers, TModule targMod) { + + return exists(usedNamespaceSpecifiers.keySet(), + is -> ((ImportDeclaration) is.eContainer()).getModule() == targMod); + } + + private static ScriptDependency createScriptDependency(Type type, + Map nameToNamedImportSpecifiers, + Map usedNamespaceSpecifiers) { + if (nameToNamedImportSpecifiers.containsKey(type.getName())) { + NamedImportSpecifier nis = nameToNamedImportSpecifiers.get(type.getName()); + TExportableElement identifiableElement = nis.getImportedElement(); + TModule module = EcoreUtil2.getContainerOfType(identifiableElement, TModule.class); + return new ScriptDependency(nis.getAlias() != null ? nis.getAlias() : identifiableElement.getName(), + identifiableElement.getName(), identifiableElement, module); + } else if (isNamespaceDependencyHandlingNeeded(usedNamespaceSpecifiers, type.getContainingModule())) { + return createDependencyOnNamespace(usedNamespaceSpecifiers, type.getContainingModule()); + } else { + // add dependency, looks like something that is @Global but not @ProvidedByRuntime + return new ScriptDependency(type.getName(), type.getName(), type, type.getContainingModule()); + } + } + + private static ScriptDependency createDependencyOnNamespace( + Map usedNamespaceSpecifiers, TModule targMod) { + NamespaceImportSpecifier is = findFirst(usedNamespaceSpecifiers.keySet(), + nis -> ((ImportDeclaration) nis.eContainer()).getModule() == targMod); + boolean used = usedNamespaceSpecifiers.get(is); + if (!used) { + // add dependency on the namespace + usedNamespaceSpecifiers.put(is, true); + return new ScriptDependency(is.getAlias(), + // For namespace imports, the actual name is intentionally null. + null, + // For namespace imports, this is ModuleNamespaceVirtualType, but intentionally null + null, + targMod); + } else { + // namespace was already used + return null; + } + } + + private static Iterable handle(EObject tClass, + Map nameToNamedImportSpecifiers, + Map usedNamespaceSpecifiers, Function compare) { + if (tClass instanceof TClass + && compare != null) { + return handle((TClass) tClass, nameToNamedImportSpecifiers, usedNamespaceSpecifiers, compare); + } else if (tClass instanceof TInterface + && compare != null) { + return handle((TInterface) tClass, nameToNamedImportSpecifiers, usedNamespaceSpecifiers, compare); + } else if (tClass instanceof N4ClassDeclaration + && compare != null) { + return handle((N4ClassDeclaration) tClass, nameToNamedImportSpecifiers, usedNamespaceSpecifiers, compare); + } else if (tClass instanceof N4InterfaceDeclaration + && compare != null) { + return handle((N4InterfaceDeclaration) tClass, nameToNamedImportSpecifiers, usedNamespaceSpecifiers, + compare); + } else if (tClass instanceof TFunction + && compare != null) { + return handle((TFunction) tClass, nameToNamedImportSpecifiers, usedNamespaceSpecifiers, compare); + } else if (tClass instanceof ParameterizedTypeRef + && compare != null) { + return handle((ParameterizedTypeRef) tClass, nameToNamedImportSpecifiers, usedNamespaceSpecifiers, compare); + } else if (tClass instanceof IdentifierRef + && compare != null) { + return handle((IdentifierRef) tClass, nameToNamedImportSpecifiers, usedNamespaceSpecifiers, compare); + } else if (tClass instanceof TypeRef + && compare != null) { + return handle((TypeRef) tClass, nameToNamedImportSpecifiers, usedNamespaceSpecifiers, compare); + } + return new ArrayList<>(); + } + + private static Iterable handle(TypeRef eo, + Map nameToNamedImportSpecifiers, + Map usedNamespaceSpecifiers, Function compare) { + + return new ArrayList<>(); + } + + private static Iterable handle(Void eo, + Map nameToNamedImportSpecifiers, + Map usedNamespaceSpecifiers, Function compare) { + + return new ArrayList<>(); + } + + private static Iterable handle(TFunction tFunction, + Map nameToNamedImportSpecifiers, + Map usedNamespaceSpecifiers, Function compare) { + // TODO is there nothing to do? + + return new ArrayList<>(); + } + + private static Iterable handle(N4ClassDeclaration eo, + Map nameToNamedImportSpecifiers, + Map usedNamespaceSpecifiers, Function compare) { + + EObject tClass = eo.getDefinedType(); + return handle(tClass, nameToNamedImportSpecifiers, usedNamespaceSpecifiers, compare); + } + + private static Iterable handle(N4InterfaceDeclaration eo, + Map nameToNamedImportSpecifiers, + Map usedNamespaceSpecifiers, Function compare) { + + EObject tInterface = eo.getDefinedType(); + return handle(tInterface, nameToNamedImportSpecifiers, usedNamespaceSpecifiers, compare); + } + + private static Iterable handle(ParameterizedTypeRef eo, + Map nameToNamedImportSpecifiers, + Map usedNamespaceSpecifiers, Function compare) { + + // added check for container instance, as polyfill tests were crashing when + // eo.declared type was TVariable and its container TClass + if (eo.getDeclaredType() != null && eo.getDeclaredType().eContainer() instanceof TModule && + compare.apply(eo.getDeclaredType())) { + String typeName = findTypeName(eo); + if (typeName != null) { // null means not typed in script (e.g. TypesComputer)-> no import necessary + TModule module = EcoreUtil2.getContainerOfType(eo.getDeclaredType(), TModule.class); + return List.of( + new ScriptDependency(typeName, eo.getDeclaredType().getName(), eo.getDeclaredType(), module)); + } + } + return new ArrayList<>(); + } + + /** + * Resolves dependency from identifier reference. + */ + private static Iterable handle(IdentifierRef idRef, + Map nameToNamedImportSpecifiers, + Map usedNamespaceSpecifiers, Function compare) { + + IdentifiableElement targetElem = idRef.getId(); + if (targetElem == null) { + // broken identifier ref? smoke tests? + return new ArrayList<>(); + } + + if (compare.apply(targetElem)) { + TModule containingModule = EcoreUtil2.getContainerOfType(targetElem, TModule.class); + return List.of( + new ScriptDependency(RefNameUtil.findIdentifierName(idRef), targetElem.getName(), targetElem, + containingModule)); + } else if (targetElem instanceof ModuleNamespaceVirtualType) { + TModule targMod = ((ModuleNamespaceVirtualType) targetElem).getModule(); + + if (isNamespaceDependencyHandlingNeeded(usedNamespaceSpecifiers, targMod)) { + return List.of(createDependencyOnNamespace(usedNamespaceSpecifiers, targMod)); + } + } + return new ArrayList<>(); + } + + private static Iterable handle(TClass tClass, + Map nameToNamedImportSpecifiers, + Map usedNamespaceSpecifiers, Function compare) { + + List deps = new ArrayList<>(); + + deps.add(tClass); + + EList interfaces = tClass.getImplementedInterfaceRefs(); + if (!isNullOrEmpty(interfaces)) + deps.addAll(toList(filterNull(map(interfaces, i -> i.getDeclaredType())))); + + ParameterizedTypeRef superClass = tClass.getSuperClassRef(); + if (superClass != null) + deps.add(superClass.getDeclaredType()); + + return map(filter(deps, it -> compare.apply(it)), + it -> createScriptDependency(it, nameToNamedImportSpecifiers, usedNamespaceSpecifiers)); + } + + private static Iterable handle(TInterface tInterface, + Map nameToNamedImportSpecifiers, + Map usedNamespaceSpecifiers, Function compare) { + + List deps = new ArrayList<>(); + + deps.add(tInterface); + + EList rs = tInterface.getSuperInterfaceRefs(); + if (!isNullOrEmpty(rs)) + deps.addAll(toList(filterNull(map(rs, it -> it.getDeclaredType())))); + + return map(filter(deps, d -> compare.apply(d)), + it -> createScriptDependency(it, nameToNamedImportSpecifiers, usedNamespaceSpecifiers)); + } + +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ScriptDependencyResolver.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ScriptDependencyResolver.xtend deleted file mode 100644 index 2117f81e99..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ScriptDependencyResolver.xtend +++ /dev/null @@ -1,339 +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.tooling.organizeImports - -import java.util.ArrayList -import java.util.Collection -import java.util.Collections -import java.util.List -import java.util.Map -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.util.EcoreUtil -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.n4JS.AnnotableElement -import org.eclipse.n4js.n4JS.IdentifierRef -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.N4ClassDeclaration -import org.eclipse.n4js.n4JS.N4InterfaceDeclaration -import org.eclipse.n4js.n4JS.N4TypeDeclaration -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType -import org.eclipse.n4js.ts.types.TAnnotableElement -import org.eclipse.n4js.ts.types.TClass -import org.eclipse.n4js.ts.types.TDynamicElement -import org.eclipse.n4js.ts.types.TFunction -import org.eclipse.n4js.ts.types.TInterface -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.ts.types.TVariable -import org.eclipse.n4js.ts.types.Type -import org.eclipse.n4js.utils.ResourceType -import org.eclipse.xtext.EcoreUtil2 - -import static extension org.eclipse.n4js.tooling.organizeImports.InjectedTypesResolverUtility.* -import static extension org.eclipse.n4js.tooling.organizeImports.RefNameUtil.* - -/** - * Static analysis for {@link Script} dependencies. Analyzes all identifiers in the {@link Script}, - * and on (on demand) {@link Type}s or {@link TypeRef}s. During analysis identifiers from {@link NamedImportSpecifier} - * and {@link NamespaceImportSpecifier} are taken into account. Note that if result analysis does not contain descriptions matching - * some of the imports, it means those imports are not used, and can be removed from script, or ignored during compilation. - * - * Result of analysis is list of {@link ScriptDependency} instances describing what {@link EObject}s should be imported. - *
    - * Note: - *
  • declarations marked with "@ProvidedByRuntime" are ignored (they are never in the result)
  • - *
  • declarations marked with "@Global" are not ignored (can show up in the result)
  • - *
- */ -class ScriptDependencyResolver { - - /** - * Resolves dependencies only from {@link IdentifierRef}s, no {@link Type}s or {@link TypeRef}s - * are taken into account. - */ - def public static List usedDependencies(Script script) { - if (null === script) return emptyList - allRequiredExternalDeclaration(script, Collections.EMPTY_SET, Collections.EMPTY_SET) - } - - /** - * Resolves dependencies from {@link IdentifierRef}s and {@link Type}s that are - * injected into local type declarations. - */ - def public static List usedDependenciesWithInejctedTypes(Script script) { - if (null === script) return emptyList - allRequiredExternalDeclaration(script, script.findAllInjected, Collections.EMPTY_SET) - } - - /** - * Resolves dependencies from {@link IdentifierRef}s and all {@link TypeRef}s. - */ - def public static List usedDependenciesTypeRefs(Script script) { - if (null === script) return emptyList - allRequiredExternalDeclaration(script, Collections.EMPTY_SET, script.eAllContents.filter(TypeRef).toList) - } - - /** - * Looks through all {@link IdentifierRef} for external dependencies - * (from different module than currently analyzed script containing module). - * Additionally looks through all types used as super types and implemented interfaces. - * Not used types (see {@link #shouldBeImported}) are removed from external dependencies. - * - * @param script to be analyzed - * @param typesToBeIncluded force specific collection of {@link Type}s to be considered for as dependencies - * @param typeRefsToBeIncluded force specific collection of {@link TypeRef}s to be considered for as dependencies - */ - def private static List allRequiredExternalDeclaration(Script script, Collection typesToBeIncluded, - Collection typeRefsToBeIncluded) { - - val indirectlyImported = script.eAllContents.filter(N4TypeDeclaration).toList - val identifierRefs = script.eAllContents.filter(IdentifierRef).toList - - val potentialDependencies = newArrayList - potentialDependencies += indirectlyImported - potentialDependencies += identifierRefs - potentialDependencies += typesToBeIncluded - potentialDependencies += typeRefsToBeIncluded - - val namedImportSpecifiers = script.eAllContents.filter(NamedImportSpecifier). - filter[it.importedElement !== null].toList - val usedNamespaceSpecifiers = script.eAllContents.filter(NamespaceImportSpecifier).toInvertedMap[false] - - val baseModule = script.module; - var depsImportedWithName = potentialDependencies.map [ - handle(namedImportSpecifiers.toMap[importedElement.name], usedNamespaceSpecifiers, - [ EObject eo | shouldBeImported(baseModule, eo) ]) - ].flatten.filterNull.toSet.toList.sortBy[localName] - - return depsImportedWithName - } - - /** - * Checks if a given EObject should be imported. - * - *
    - * Evaluates to true if: - *
  • provided EO is not from the module provided at creation time (that module is assumed to be one for which we analyze dependencies)
  • - *
  • provided EO is not from built in types
  • - *
  • (in case of AST elements) is not annotated with {@link AnnotationDefinition.PROVIDED_BY_RUNTIME)
  • - *
  • (in case of TS elements) providedByRuntime evaluates to false
  • - *
- * - * @returns true if given EO should be imported - */ - def public static boolean shouldBeImported(TModule baseModule, EObject eo) { - if (eo instanceof ModuleNamespaceVirtualType - || eo instanceof TDynamicElement) { - return true; - } - - val containingModule = EcoreUtil.getRootContainer(eo); - if (containingModule instanceof TModule) { - if (AnnotationDefinition.PROVIDED_BY_RUNTIME.hasAnnotation(containingModule) - || (ResourceType.getResourceType(containingModule) === ResourceType.DTS && AnnotationDefinition.GLOBAL.hasAnnotation(containingModule))) { - return false; - } - } - - if (eo instanceof AnnotableElement) { - if (AnnotationDefinition.PROVIDED_BY_RUNTIME.hasAnnotation(eo)) { - return false; - } - } else if (eo instanceof TAnnotableElement) { - if (AnnotationDefinition.PROVIDED_BY_RUNTIME.hasAnnotation(eo)) { - return false; - } else if (eo instanceof Type) { - // TODO is this dead code - if (eo.providedByRuntime) { - return false; - } - } else if (eo instanceof TVariable) { - // TODO is this dead code - if (eo.providedByRuntime) { - return false; - } - } - } - - // ignore built-in things as n4scheme:/console.n4jsd: - // in non platform realm check if URI describes file, - // in eclipse platform realm check if URI describes platform resource - return eo.eResource?.getURI !== null // - && ((eo.eResource.getURI.file || eo.eResource.getURI.platformResource)) // - // and check if modules/files are different - && (! eo.eResource.getURI.toString.equals(baseModule.eResource.getURI.toString)) - } - - def private static boolean isNamespaceDependencyHandlingNeeded( - Map usedNamespaceSpecifiers, TModule targMod) { - - return usedNamespaceSpecifiers.keySet.exists[is|(is.eContainer as ImportDeclaration).module === targMod] - } - - def private static createScriptDependency(Type type, Map nameToNamedImportSpecifiers, - Map usedNamespaceSpecifiers) { - if (nameToNamedImportSpecifiers.containsKey(type.name)) { - val nis = nameToNamedImportSpecifiers.get(type.name); - val identifiableElement = nis.importedElement; - val module = EcoreUtil2.getContainerOfType(identifiableElement, TModule); - new ScriptDependency(nis.alias ?: identifiableElement.name, identifiableElement.name, identifiableElement, module); - } else if (isNamespaceDependencyHandlingNeeded(usedNamespaceSpecifiers, type.containingModule)) { - createDependencyOnNamespace(usedNamespaceSpecifiers, type.containingModule) - } else { - // add dependency, looks like something that is @Global but not @ProvidedByRuntime - new ScriptDependency(type.name, type.name, type, type.containingModule) - } - } - - def private static ScriptDependency createDependencyOnNamespace( - Map usedNamespaceSpecifiers, TModule targMod) { - val is = usedNamespaceSpecifiers.keySet.findFirst [ is | - (is.eContainer as ImportDeclaration).module === targMod - ] - val used = usedNamespaceSpecifiers.get(is) - if (!used) { - // add dependency on the namespace - usedNamespaceSpecifiers.put(is, true) - new ScriptDependency(is.alias, null, // For namespace imports, the actual name is intentionally null. - null, // For namespace imports, this is ModuleNamespaceVirtualType, but intentionally null - targMod) - } else { - // namespace was already used - null - } - } - - def private static dispatch Iterable handle(EObject eo, - Map nameToNamedImportSpecifiers, - Map usedNamespaceSpecifiers, (EObject)=>boolean compare) { - - return newArrayList() - } - - def private static dispatch Iterable handle(TypeRef eo, - Map nameToNamedImportSpecifiers, - Map usedNamespaceSpecifiers, (EObject)=>boolean compare) { - - return newArrayList() - } - - def private static dispatch Iterable handle(Void eo, - Map nameToNamedImportSpecifiers, - Map usedNamespaceSpecifiers, (EObject)=>boolean compare) { - - return newArrayList() - } - - def private static dispatch Iterable handle(TFunction tFunction, - Map nameToNamedImportSpecifiers, - Map usedNamespaceSpecifiers, (EObject)=>boolean compare) { - // TODO is there nothing to do? - - return newArrayList() - } - - def private static dispatch Iterable handle(N4ClassDeclaration eo, - Map nameToNamedImportSpecifiers, - Map usedNamespaceSpecifiers, (EObject)=>boolean compare) { - - val tClass = eo.definedType as TClass - handle(tClass, nameToNamedImportSpecifiers, usedNamespaceSpecifiers, compare) - } - - def private static dispatch Iterable handle(N4InterfaceDeclaration eo, - Map nameToNamedImportSpecifiers, - Map usedNamespaceSpecifiers, (EObject)=>boolean compare) { - - val tInterface = eo.definedType as TInterface - handle(tInterface, nameToNamedImportSpecifiers, usedNamespaceSpecifiers, compare) - } - - def private static dispatch Iterable handle(ParameterizedTypeRef eo, - Map nameToNamedImportSpecifiers, - Map usedNamespaceSpecifiers, (EObject)=>boolean compare) { - - // added check for container instance, as polyfill tests were crashing when - // eo.declared type was TVariable and its container TClass - if (eo.declaredType !== null && eo.declaredType.eContainer instanceof TModule && - compare.apply(eo.declaredType)) { - val typeName = eo.findTypeName - if (typeName !== null) { // null means not typed in script (e.g. TypesComputer)-> no import necessary - val module = EcoreUtil2.getContainerOfType(eo.declaredType, TModule); - return newArrayList( - new ScriptDependency(typeName, eo.declaredType.name, eo.declaredType, module)) - } - } - return newArrayList() - } - - /** - * Resolves dependency from identifier reference. - */ - def private static dispatch Iterable handle(IdentifierRef idRef, - Map nameToNamedImportSpecifiers, - Map usedNamespaceSpecifiers, (EObject)=>boolean compare) { - - val targetElem = idRef.id; - if(targetElem === null){ - //broken identifier ref? smoke tests? - return newArrayList() - } - - if (compare.apply(targetElem)) { - val containingModule = EcoreUtil2.getContainerOfType(targetElem, TModule); - return newArrayList( - new ScriptDependency(idRef.findIdentifierName, targetElem.name, targetElem, containingModule)) - } else if (targetElem instanceof ModuleNamespaceVirtualType) { - val targMod = targetElem.module - - if (isNamespaceDependencyHandlingNeeded(usedNamespaceSpecifiers, targMod)) { - return newArrayList(createDependencyOnNamespace(usedNamespaceSpecifiers, targMod)) - } - } - return newArrayList() - } - - def private static dispatch Iterable handle(TClass tClass, - Map nameToNamedImportSpecifiers, - Map usedNamespaceSpecifiers, (EObject)=>boolean compare) { - - val deps = new ArrayList() - - deps.add(tClass) - - val interfaces = tClass.implementedInterfaceRefs - if (!interfaces.nullOrEmpty) deps.addAll(interfaces.map[declaredType].filterNull) - - val superClass = tClass.superClassRef - if (superClass !== null) deps.add(superClass.declaredType) - - deps.filter[compare.apply(it)].map[createScriptDependency(nameToNamedImportSpecifiers, usedNamespaceSpecifiers)] - } - - def private static dispatch Iterable handle(TInterface tInterface, - Map nameToNamedImportSpecifiers, - Map usedNamespaceSpecifiers, (EObject)=>boolean compare) { - - val deps = new ArrayList() - - deps.add(tInterface) - - val rs = tInterface.superInterfaceRefs - if (!rs.nullOrEmpty) deps.addAll(rs.map[declaredType].filterNull) - - deps.filter[compare.apply(it)].map[createScriptDependency(nameToNamedImportSpecifiers, usedNamespaceSpecifiers)] - } - -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/react/ReactHelper.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/react/ReactHelper.java new file mode 100644 index 0000000000..bd8d130f36 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/react/ReactHelper.java @@ -0,0 +1,396 @@ +/** + * 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.tooling.react; + +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.anyTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.newRuleEnvironment; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.JSXElement; +import org.eclipse.n4js.resource.N4JSCache; +import org.eclipse.n4js.resource.N4JSResource; +import org.eclipse.n4js.scoping.N4JSScopeProvider; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExprOrRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeTypeRef; +import org.eclipse.n4js.ts.types.ElementExportDefinition; +import org.eclipse.n4js.ts.types.ExportDefinition; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TField; +import org.eclipse.n4js.ts.types.TFormalParameter; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.TGetter; +import org.eclipse.n4js.ts.types.TInterface; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.ts.types.TNamespace; +import org.eclipse.n4js.ts.types.TVariable; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.ts.types.TypeVariable; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.typesystem.N4JSTypeSystem; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.TypeSystemHelper; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.scoping.IScope; +import org.eclipse.xtext.scoping.IScopeProvider; +import org.eclipse.xtext.xbase.lib.StringExtensions; + +import com.google.inject.Inject; + +/** + * This helper provides utilities for looking up React definitions such as React.Component or React.ReactElement or for + * calculating types related to React (e.g. of props property) etc. + */ +public class ReactHelper { + @Inject + private N4JSTypeSystem ts; + @Inject + private TypeSystemHelper tsh; + @Inject + private N4JSCache cache; + @Inject + private IScopeProvider scopeProvider; + + /***/ + public final static String REACT_PROJECT_ID = "react"; + /***/ + public final static String REACT_COMPONENT = "Component"; + /***/ + public final static String REACT_ELEMENT = "ReactElement"; + /***/ + public final static String REACT_FRAGMENT_NAME = "Fragment"; + + /***/ + public final static String REACT_NAMESPACE_NAME = StringExtensions.toFirstUpper(REACT_PROJECT_ID); + /***/ + public final static String REACT_JSX_TRANSFORM_NAME = "jsx"; + /***/ + public final static String REACT_JSXS_TRANSFORM_NAME = "jsxs"; + /***/ + public final static String REACT_ELEMENT_FACTORY_FUNCTION_NAME = "createElement"; + /***/ + public final static String REACT_ELEMENT_PROPERTY_KEY_NAME = "key"; + /***/ + public final static String REACT_ELEMENT_PROPERTY_CHILDREN_NAME = "children"; + /***/ + public final static String REACT_JSX_RUNTIME_NAME = "react/jsx-runtime"; + + private final static String REACT_KEY = "KEY__" + REACT_PROJECT_ID; + + /** + * Returns the {@link TModule} which contains the definitions of the React JSX backend (e.g. {@code Component}, + * {@code createElement}). + * + * Returns {@code null} if no valid React implementation can be found in the index. + */ + public TModule getJsxBackendModule(Resource resource) { + String key = REACT_KEY + "." + "TMODULE"; + return cache.get(resource, () -> { + IScope scope = ((N4JSScopeProvider) scopeProvider).getScopeForImplicitImports((N4JSResource) resource); + Iterable matchingDescriptions = scope + .getElements(QualifiedName.create(REACT_PROJECT_ID)); + // resolve all found 'react.js' files, until a valid react implementation is found + for (IEObjectDescription desc : matchingDescriptions) { + if (desc != null) { + // filter for valid react modules only + TModule module = (TModule) EcoreUtil.resolve(desc.getEObjectOrProxy(), resource); + if (isValidReactModule(module)) { + return module; + } + } + } + return null; + }, key); + } + + /** + * Returns the preferred name of the namespace used to import the JSX backend. + */ + public String getJsxBackendNamespaceName() { + return REACT_NAMESPACE_NAME; + } + + /** + * Returns the name of the element factory function to use with React as JSX backend. + */ + public String getJsxBackendElementFactoryFunctionName() { + return REACT_ELEMENT_FACTORY_FUNCTION_NAME; + } + + /** + * Calculate the type that an JSX element is binding to, usually class/function type + * + * @param jsxElem + * the input JSX element + * @return the {@link TypeRef} that the JSX element is binding to and null if not found + */ + public TypeRef getJsxElementBindingType(JSXElement jsxElem) { + Expression expr = jsxElem.getJsxElementName().getExpression(); + RuleEnvironment G = newRuleEnvironment(expr); + TypeRef exprResult = ts.type(G, expr); + return exprResult; + } + + /** + * Returns the element factory function for JSX elements which can be extracted from the given {@link Resource}. + */ + public TFunction getJsxBackendElementFactoryFunction(Resource resource) { + TModule module = this.getJsxBackendModule(resource); + if (module != null) { + return lookUpReactElementFactoryFunction(module); + } + return null; + } + + /** + * Returns the fragment factory function for JSX elements which can be extracted from the given {@link Resource}. + */ + public IdentifiableElement getJsxBackendFragmentComponent(Resource resource) { + TModule module = this.getJsxBackendModule(resource); + if (module != null) { + return lookUpReactFragmentComponent(module); + } + return null; + } + + /** + * Look up React.Element in the index. + * + * @param context + * the EObject serving the context to look for React.Element. + */ + public TClassifier lookUpReactElement(EObject context) { + // val reactElement = lookUpReactClassifier(context, REACT_ELEMENT, TInterface) + // if (reactElement !== null) { + // return reactElement; + // } + return lookUpReactClassifier_OLD(context, REACT_ELEMENT, TInterface.class); + } + + /** + * Look up React.Component in the index. + * + * @param context + * the EObject serving the context to look for React.Component. + */ + public TClass lookUpReactComponent(EObject context) { + // val reactComponent = lookUpReactClassifier(context, REACT_COMPONENT, TClass); + // if (reactComponent !== null) { + // return reactComponent; + // } + return lookUpReactClassifier_OLD(context, REACT_COMPONENT, TClass.class); + } + + /** + * Calculate the "props" type of an JSX element. It is either the first type parameter of React.Component class or + * the type of the first parameter of a functional React component + * + * @param jsxElem + * the input JSX element + * @return the {@link TypeRef} if exists and null otherwise + */ + public TypeRef getPropsType(JSXElement jsxElem) { + TypeRef exprTypeRef = getJsxElementBindingType(jsxElem); + if (exprTypeRef == null) { + return null; + } + + RuleEnvironment G = newRuleEnvironment(jsxElem); + if (exprTypeRef instanceof TypeTypeRef && ((TypeTypeRef) exprTypeRef).isConstructorRef()) { + // The JSX element refers to a class + Type tclass = tsh.getStaticType(G, (TypeTypeRef) exprTypeRef); + TClass tComponentClass = lookUpReactComponent(jsxElem); + if (tComponentClass == null || tComponentClass.getTypeVars().isEmpty()) { + return null; + } + TypeVariable reactComponentProps = tComponentClass.getTypeVars().get(0); + // Add type variable -> type argument mappings from the current and all super types + tsh.addSubstitutions(G, TypeUtils.createTypeRef(tclass)); + // Substitute type variables in the 'props' and return the result + // Note: after substTypeVariablesInTypeRef is called, the rule environment G is unchanged so do not ask G + // for result as this caused bug IDE-2540 + TypeRef reactComponentPropsTypeRef = ts.substTypeVariables(G, + TypeUtils.createTypeRef(reactComponentProps)); + return reactComponentPropsTypeRef; + + } else if (exprTypeRef instanceof FunctionTypeExprOrRef) { + FunctionTypeExprOrRef fteor = (FunctionTypeExprOrRef) exprTypeRef; + // The JSX elements refers to a function, assume that the first parameter is props + if (fteor.getFpars().size() > 0) { + TFormalParameter tPropsParam = fteor.getFpars().get(0); + return tPropsParam.getTypeRef(); + } + } + return null; + } + + /***/ + public TypeRef getConstructorFunctionType(JSXElement jsxElem) { + RuleEnvironment G = newRuleEnvironment(jsxElem); + TypeRef returnTypeRef = getJsxElementBindingType(jsxElem); + if (returnTypeRef instanceof TypeTypeRef && ((TypeTypeRef) returnTypeRef).getTypeArg() instanceof TypeRef) { + returnTypeRef = (TypeRef) ((TypeTypeRef) returnTypeRef).getTypeArg(); + } + List args = Collections.singletonList(anyTypeRef(G)); + return TypeUtils.createFunctionTypeExpression(args, returnTypeRef); + } + + /** + * Return the type of a field or return type of a getter. + * + * @param member + * MUST be either a field or getter (otherwise an exception is thrown). + */ + public TypeRef typeRefOfFieldOrGetter(TMember member, TypeRef context) { + if (member instanceof TField || member instanceof TGetter) + return ts.tau(member, context); + + throw new IllegalArgumentException(member + " must be either a TField or TGetter"); + } + + /** + * Lookup React component/element type. For increased efficiency, the found results are cached. + * + * @param context + * the EObject serving the context to look for React classifiers. + * @param reactClassifierName + * the name of React classifier. + */ + // TODO: continue work on use of TypeScript type definitions of React + @SuppressWarnings("unchecked") + private T lookUpReactClassifier(EObject context, String reactClassifierName, + Class clazz) { + Resource resource = context.eResource(); + TModule tModule = getJsxBackendModule(resource); + if (tModule == null || tModule.eResource() == null) { + return null; + } + + String key = REACT_KEY + "." + reactClassifierName; + return cache.get(tModule.eResource(), () -> { + // used for @types/react + for (ExportDefinition expDef : tModule.getExportDefinitions()) { + if (expDef instanceof ElementExportDefinition) { + ElementExportDefinition eed = (ElementExportDefinition) expDef; + if ("React".equals(eed.getExportedName()) && eed.getExportedElement() instanceof TNamespace) { + for (Type type : ((TNamespace) eed.getExportedElement()).getTypes()) { + if (clazz.isAssignableFrom(type.getClass()) && reactClassifierName.equals(type.getName())) { + return (T) type; + } + } + } + } + } + return null; + }, key); + } + + private T lookUpReactClassifier_OLD(EObject context, String reactClassifierName, + Class clazz) { + Resource resource = context.eResource(); + TModule tModule = getJsxBackendModule(resource); + if (tModule == null || tModule.eResource() == null) { + return null; + } + + String key = REACT_KEY + "_OLD." + reactClassifierName; + return cache.get(tModule.eResource(), () -> { + // used for @n4jsd/react + T tClassifier = findFirst(filter(tModule.getTypes(), clazz), + c -> Objects.equals(c.getName(), reactClassifierName)); + return tClassifier; + }, key); + } + + private IdentifiableElement lookUpReactFragmentComponent(TModule module) { + if (module != null) { + // used for @types/react + for (ExportDefinition expDef : module.getExportDefinitions()) { + if (expDef instanceof ElementExportDefinition) { + ElementExportDefinition eed = (ElementExportDefinition) expDef; + if ("React".equals(eed.getExportedName()) && eed.getExportedElement() instanceof TNamespace) { + for (TVariable currTopLevelVar : ((TNamespace) eed.getExportedElement()) + .getExportedVariables()) { + if (REACT_FRAGMENT_NAME.equals(currTopLevelVar.getName())) { + return currTopLevelVar; + } + } + } + } + } + // used for @n4jsd/react + for (Type currTopLevelType : module.getTypes()) { + if (currTopLevelType instanceof TClass + && REACT_FRAGMENT_NAME.equals(currTopLevelType.getName())) { + return currTopLevelType; + } + } + } + return null; + } + + /** + * Looks up the element factory function to use, assuming the react implementation in use is to be found in the + * given {@code module}. + * + * Returns {@code null} if no factory function can be found in the given module. + */ + private TFunction lookUpReactElementFactoryFunction(TModule module) { + if (module != null) { + // used for @types/react + for (ExportDefinition expDef : module.getExportDefinitions()) { + if (expDef instanceof ElementExportDefinition) { + ElementExportDefinition eed = (ElementExportDefinition) expDef; + if ("React".equals(eed.getExportedName()) && eed.getExportedElement() instanceof TNamespace) { + for (TFunction currTopLevelType : ((TNamespace) eed.getExportedElement()).getFunctions()) { + if (REACT_ELEMENT_FACTORY_FUNCTION_NAME.equals(currTopLevelType.getName())) { + return currTopLevelType; + } + } + } + } + } + // used for @n4jsd/react + for (TFunction currTopLevelType : module.getFunctions()) { + if (REACT_ELEMENT_FACTORY_FUNCTION_NAME.equals(currTopLevelType.getName())) { + return currTopLevelType; + } + } + } + return null; + } + + /** + * Returns {@code true} if the given {@code module} is a {@link TModule} that represents a valid implementation of + * React. + * + * Returns {@code false} otherwise. + * + * Note that this requires type definitions to be available for the given {@link TModule}. + */ + private boolean isValidReactModule(TModule module) { + // check for existence of the element factory function + return lookUpReactElementFactoryFunction(module) != null; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/react/ReactHelper.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/react/ReactHelper.xtend deleted file mode 100644 index 23f11003e8..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/react/ReactHelper.xtend +++ /dev/null @@ -1,344 +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.tooling.react - -import com.google.inject.Inject -import java.util.Collections -import java.util.List -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.n4js.n4JS.JSXElement -import org.eclipse.n4js.resource.N4JSCache -import org.eclipse.n4js.resource.N4JSResource -import org.eclipse.n4js.scoping.N4JSScopeProvider -import org.eclipse.n4js.ts.typeRefs.FunctionTypeExprOrRef -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.typeRefs.TypeTypeRef -import org.eclipse.n4js.ts.types.ElementExportDefinition -import org.eclipse.n4js.ts.types.ExportDefinition -import org.eclipse.n4js.ts.types.IdentifiableElement -import org.eclipse.n4js.ts.types.TClass -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TField -import org.eclipse.n4js.ts.types.TFunction -import org.eclipse.n4js.ts.types.TGetter -import org.eclipse.n4js.ts.types.TInterface -import org.eclipse.n4js.ts.types.TMember -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.ts.types.TNamespace -import org.eclipse.n4js.ts.types.TVariable -import org.eclipse.n4js.ts.types.Type -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.typesystem.N4JSTypeSystem -import org.eclipse.n4js.typesystem.utils.TypeSystemHelper -import org.eclipse.xtext.EcoreUtil2 -import org.eclipse.xtext.naming.QualifiedName -import org.eclipse.xtext.scoping.IScopeProvider - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * This helper provides utilities for looking up React definitions such as React.Component or React.ReactElement or - * for calculating types related to React (e.g. of props property) etc. - */ -class ReactHelper { - @Inject private N4JSTypeSystem ts - @Inject private TypeSystemHelper tsh - @Inject private N4JSCache cache - @Inject private IScopeProvider scopeProvider; - - public final static String REACT_PROJECT_ID = "react" - public final static String REACT_COMPONENT = "Component" - public final static String REACT_ELEMENT = "ReactElement" - public final static String REACT_FRAGMENT_NAME = "Fragment"; - - public final static String REACT_NAMESPACE_NAME = REACT_PROJECT_ID.toFirstUpper; - public final static String REACT_JSX_TRANSFORM_NAME = "jsx"; - public final static String REACT_JSXS_TRANSFORM_NAME = "jsxs"; - public final static String REACT_ELEMENT_FACTORY_FUNCTION_NAME = "createElement"; - public final static String REACT_ELEMENT_PROPERTY_KEY_NAME = "key"; - public final static String REACT_ELEMENT_PROPERTY_CHILDREN_NAME = "children"; - public final static String REACT_JSX_RUNTIME_NAME = "react/jsx-runtime"; - - - private final static String REACT_KEY = "KEY__" + REACT_PROJECT_ID - - /** - * Returns the {@link TModule} which contains the definitions of the React - * JSX backend (e.g. {@code Component}, {@code createElement}). - * - * Returns {@code null} if no valid React implementation can be found in the index. - */ - def public TModule getJsxBackendModule(Resource resource) { - val String key = REACT_KEY + "." + "TMODULE"; - return cache.get(resource, [ - val scope = (scopeProvider as N4JSScopeProvider).getScopeForImplicitImports(resource as N4JSResource); - val matchingDescriptions = scope.getElements(QualifiedName.create(REACT_PROJECT_ID)); - // resolve all found 'react.js' files, until a valid react implementation is found - return matchingDescriptions.map[desc | - if(desc === null) - return null - return EcoreUtil2.resolve(desc.EObjectOrProxy, resource) as TModule - ] - // filter for valid react modules only - .filter[module | module.isValidReactModule].head; - ], key); - } - - /** - * Returns the preferred name of the namespace used to import the JSX backend. - */ - def public String getJsxBackendNamespaceName() { - return REACT_NAMESPACE_NAME; - } - - /** - * Returns the name of the element factory function to use with React as JSX backend. - */ - def getJsxBackendElementFactoryFunctionName() { - return REACT_ELEMENT_FACTORY_FUNCTION_NAME; - } - - /** - * Calculate the type that an JSX element is binding to, usually class/function type - * - * @param jsxElem the input JSX element - * @return the typeref that the JSX element is binding to and null if not found - */ - def public TypeRef getJsxElementBindingType(JSXElement jsxElem) { - val expr = jsxElem.jsxElementName.expression; - val G = expr.newRuleEnvironment; - val exprResult = ts.type(G, expr); - return exprResult; - } - - /** - * Returns the element factory function for JSX elements which can be extracted - * from the given {@link Resource}. - */ - def public TFunction getJsxBackendElementFactoryFunction(Resource resource) { - val module = this.getJsxBackendModule(resource); - if (module !== null) { - return lookUpReactElementFactoryFunction(module); - } - return null; - } - - /** - * Returns the fragment factory function for JSX elements which can be extracted - * from the given {@link Resource}. - */ - def public IdentifiableElement getJsxBackendFragmentComponent(Resource resource) { - val module = this.getJsxBackendModule(resource); - if (module !== null) { - return lookUpReactFragmentComponent(module); - } - return null; - } - - /** - * Look up React.Element in the index. - * - * @param context the EObject serving the context to look for React.Element. - */ - def public TClassifier lookUpReactElement(EObject context) { -// val reactElement = lookUpReactClassifier(context, REACT_ELEMENT, TInterface) -// if (reactElement !== null) { -// return reactElement; -// } - return lookUpReactClassifier_OLD(context, REACT_ELEMENT, TInterface); - } - - /** - * Look up React.Component in the index. - * - * @param context the EObject serving the context to look for React.Component. - */ - def public TClass lookUpReactComponent(EObject context) { -// val reactComponent = lookUpReactClassifier(context, REACT_COMPONENT, TClass); -// if (reactComponent !== null) { -// return reactComponent; -// } - return lookUpReactClassifier_OLD(context, REACT_COMPONENT, TClass); - } - - /** - * Calculate the "props" type of an JSX element. It is either the first type parameter of React.Component class or - * the type of the first parameter of a functional React component - * - * @param jsxElement the input JSX element - * @return the typeref if exists and null otherwise - */ - def public TypeRef getPropsType(JSXElement jsxElem) { - val TypeRef exprTypeRef = jsxElem.jsxElementBindingType - if (exprTypeRef === null) - return null; - - val G = jsxElem.newRuleEnvironment; - if (exprTypeRef instanceof TypeTypeRef && (exprTypeRef as TypeTypeRef).constructorRef) { - // The JSX element refers to a class - val tclass = tsh.getStaticType(G, exprTypeRef as TypeTypeRef); - val tComponentClass = lookUpReactComponent(jsxElem); - if (tComponentClass === null || tComponentClass.typeVars.isEmpty) { - return null; - } - val reactComponentProps = tComponentClass.typeVars.get(0); - // Add type variable -> type argument mappings from the current and all super types - tsh.addSubstitutions(G, TypeUtils.createTypeRef(tclass)); - // Substitute type variables in the 'props' and return the result - // Note: after substTypeVariablesInTypeRef is called, the rule environment G is unchanged so do not ask G for result as this caused bug IDE-2540 - val reactComponentPropsTypeRef = ts.substTypeVariables(G, - TypeUtils.createTypeRef(reactComponentProps)); - return reactComponentPropsTypeRef; - - } else if (exprTypeRef instanceof FunctionTypeExprOrRef) { - // The JSX elements refers to a function, assume that the first parameter is props - if (exprTypeRef.fpars.length > 0) { - val tPropsParam = exprTypeRef.fpars.get(0); - return tPropsParam.typeRef - } - } - return null; - } - - def public TypeRef getConstructorFunctionType(JSXElement jsxElem) { - val G = newRuleEnvironment(jsxElem); - var TypeRef returnTypeRef = getJsxElementBindingType(jsxElem); - if (returnTypeRef instanceof TypeTypeRef && (returnTypeRef as TypeTypeRef).typeArg instanceof TypeRef) { - returnTypeRef = (returnTypeRef as TypeTypeRef).typeArg as TypeRef; - } - val List args = Collections.singletonList(anyTypeRef(G)); - return TypeUtils.createFunctionTypeExpression(args, returnTypeRef); - } - - /** - * Return the type of a field or return type of a getter. - * - * @param member MUST be either a field or getter (otherwise an exception is thrown). - */ - def public TypeRef typeRefOfFieldOrGetter(TMember member, TypeRef context) { - if (member instanceof TField || member instanceof TGetter) - return ts.tau(member, context); - - throw new IllegalArgumentException(member + " must be either a TField or TGetter"); - } - - /** - * Lookup React component/element type. For increased efficiency, the found results are cached. - * - * @param context the EObject serving the context to look for React classifiers. - * @param reactClassifierName the name of React classifier. - */ - // TODO: continue work on use of TypeScript type definitions of React - def private T lookUpReactClassifier(EObject context, String reactClassifierName, Class clazz) { - val resource = context.eResource; - val tModule = getJsxBackendModule(resource); - if (tModule === null || tModule.eResource === null) - return null; - - val String key = REACT_KEY + "." + reactClassifierName; - return cache.get(tModule.eResource, [ - // used for @types/react - for (ExportDefinition expDef : tModule.exportDefinitions) { - if (expDef instanceof ElementExportDefinition) { - if (expDef.exportedName == "React" && expDef.exportedElement instanceof TNamespace) { - for (Type type : (expDef.exportedElement as TNamespace).types) { - if (clazz.isAssignableFrom(type.class) && reactClassifierName.equals(type.getName())) { - return type as T; - } - } - } - } - } - return null; - ], key); - } - def private T lookUpReactClassifier_OLD(EObject context, String reactClassifierName, Class clazz) { - val resource = context.eResource; - val tModule = getJsxBackendModule(resource); - if (tModule === null || tModule.eResource === null) - return null; - - val String key = REACT_KEY + "_OLD." + reactClassifierName; - return cache.get(tModule.eResource, [ - // used for @n4jsd/react - val tClassifier = tModule.types.filter(clazz).findFirst[name == reactClassifierName]; - return tClassifier; - ], key); - } - - def private IdentifiableElement lookUpReactFragmentComponent(TModule module) { - if (module !== null) { - // used for @types/react - for (ExportDefinition expDef : module.exportDefinitions) { - if (expDef instanceof ElementExportDefinition) { - if (expDef.exportedName == "React" && expDef.exportedElement instanceof TNamespace) { - for (TVariable currTopLevelVar : (expDef.exportedElement as TNamespace).exportedVariables) { - if (REACT_FRAGMENT_NAME.equals(currTopLevelVar.getName())) { - return currTopLevelVar; - } - } - } - } - } - // used for @n4jsd/react - for (Type currTopLevelType : module.getTypes()) { - if (currTopLevelType instanceof TClass - && REACT_FRAGMENT_NAME.equals(currTopLevelType.getName())) { - return currTopLevelType as TClass; - } - } - } - return null; - } - /** - * Looks up the element factory function to use, assuming the react implementation in use - * is to be found in the given {@code module}. - * - * Returns {@code null} if no factory function can be found in the given module. - */ - def private TFunction lookUpReactElementFactoryFunction(TModule module) { - if (module !== null) { - // used for @types/react - for (ExportDefinition expDef : module.exportDefinitions) { - if (expDef instanceof ElementExportDefinition) { - if (expDef.exportedName == "React" && expDef.exportedElement instanceof TNamespace) { - for (TFunction currTopLevelType : (expDef.exportedElement as TNamespace).getFunctions()) { - if (REACT_ELEMENT_FACTORY_FUNCTION_NAME.equals(currTopLevelType.getName())) { - return currTopLevelType; - } - } - } - } - } - // used for @n4jsd/react - for (TFunction currTopLevelType : module.getFunctions()) { - if (REACT_ELEMENT_FACTORY_FUNCTION_NAME.equals(currTopLevelType.getName())) { - return currTopLevelType; - } - } - } - return null; - } - - /** - * Returns {@code true} if the given {@code module} is a {@link TModule} - * that represents a valid implementation of React. - * - * Returns {@code false} otherwise. - * - * Note that this requires type definitions to be available for the given {@link TModule}. - */ - def private boolean isValidReactModule(TModule module) { - // check for existence of the element factory function - return lookUpReactElementFactoryFunction(module) !== null; - } -} 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 { + if (formalParameterTypesBuilder.relinkFormalParameter(fpar, functionType, builtInTypeScope, preLinkingPhase, + idx)) { + return idx + 1; + } + return idx; + }); + } + + protected void addFormalParameters(TFunction functionType, FunctionDefinition functionDef, + BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { + functionType.getFpars().addAll( + toList(filterNull(map(functionDef.getFpars(), + fp -> formalParameterTypesBuilder.createFormalParameter(fp, builtInTypeScope, + preLinkingPhase))))); + } + + protected void setReturnType(TGetter getterType, N4GetterDeclaration getterDef, + BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { + if (!preLinkingPhase) { + TypeRef inferredReturnTypeRef = null; + if (getterDef.getDeclaredTypeRefInAST() == null) { + if (!preLinkingPhase) { + if (getterType.isAbstract()) { + inferredReturnTypeRef = builtInTypeScope.getAnyTypeRef(); + } else { + inferredReturnTypeRef = inferReturnTypeFromReturnStatements(getterDef, builtInTypeScope); + } + } + } else { + inferredReturnTypeRef = getterDef.getDeclaredTypeRefInAST(); + } + getterType.setTypeRef(TypeUtils.copyWithProxies(inferredReturnTypeRef)); + } + } + + protected void setReturnType(TFunction functionType, FunctionDefinition functionDef, + BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { + if (!preLinkingPhase) { + TypeRef inferredReturnTypeRef = null; + if (functionDef.getDeclaredReturnTypeRefInAST() == null) { + if (!preLinkingPhase) { + inferredReturnTypeRef = inferReturnTypeFromReturnStatements(functionDef, builtInTypeScope); + } + } else { + inferredReturnTypeRef = functionDef.getDeclaredReturnTypeRefInAST(); + } + functionType.setReturnTypeRef(TypeUtils.copyWithProxies(inferredReturnTypeRef)); + // note: handling of the return type of async functions not done here, see + // TypeProcessor#handleAsyncFunctionDeclaration() + } + } + + /** + * Poor man's return type inferencer + */ + // TODO improve that + protected ParameterizedTypeRef inferReturnTypeFromReturnStatements(FunctionDefinition definition, + BuiltInTypeScope builtInTypeScope) { + boolean hasNonVoidReturn = definition.getBody() != null && definition.getBody().hasNonVoidReturn(); + if (hasNonVoidReturn) { + return builtInTypeScope.getAnyTypeRef(); + } else { + /* + * No Return statements usually implies void as result type for the FunctionDefinition, except for those + * representing arrow functions of the single-expression variety, whose result type is heuristically + * approximated as 'any'. + * + * FIXME that single-expr in an arrow function may well be an invocation to a void-method, in which case the + * 'any' choice is wrong. + */ + if (isSingleExprArrowFunction(definition)) { + return builtInTypeScope.getAnyTypeRef(); + } else { + return builtInTypeScope.getVoidTypeRef(); + } + } + } + + private boolean isSingleExprArrowFunction(FunctionDefinition definition) { + if (definition instanceof ArrowFunction) { + return ((ArrowFunction) definition).isSingleExprImplicitReturn(); + } + return false; + } + + /** + * Poor man's return type inferencer + */ + // TODO improve that + protected ParameterizedTypeRef inferReturnTypeFromReturnStatements(N4GetterDeclaration definition, + BuiltInTypeScope builtInTypeScope) { + boolean hasNonVoidReturn = definition.getBody() != null && definition.getBody().hasNonVoidReturn(); + if (hasNonVoidReturn) { + return builtInTypeScope.getAnyTypeRef(); + } else { + return builtInTypeScope.getVoidTypeRef(); + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/AbstractFunctionDefinitionTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/AbstractFunctionDefinitionTypesBuilder.xtend deleted file mode 100644 index fb35a576e4..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/AbstractFunctionDefinitionTypesBuilder.xtend +++ /dev/null @@ -1,125 +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.typesbuilder - -import com.google.inject.Inject -import org.eclipse.n4js.n4JS.ArrowFunction -import org.eclipse.n4js.n4JS.FunctionDefinition -import org.eclipse.n4js.n4JS.N4GetterDeclaration -import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.types.TFunction -import org.eclipse.n4js.ts.types.TGetter -import org.eclipse.n4js.types.utils.TypeUtils - -/** - * Base class for functions and methods - */ -package abstract class AbstractFunctionDefinitionTypesBuilder { - - @Inject extension N4JSFormalParameterTypesBuilder - - def protected void relinkFormalParameters(TFunction functionType, FunctionDefinition functionDef, boolean preLinkingPhase) { - val builtInTypeScope = BuiltInTypeScope.get(functionDef.eResource.resourceSet) - functionDef.fpars.fold(0) [ idx, fpar | - if (relinkFormalParameter(fpar, functionType, builtInTypeScope, preLinkingPhase, idx)) { - return idx + 1; - } - return idx; - ] - } - - def protected void addFormalParameters(TFunction functionType, FunctionDefinition functionDef, - BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - functionType.fpars.addAll( - functionDef.fpars.map[createFormalParameter(builtInTypeScope, preLinkingPhase)].filterNull); - } - - def protected void setReturnType(TGetter getterType, N4GetterDeclaration getterDef, - BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - if (!preLinkingPhase) { - val inferredReturnTypeRef = - if (getterDef.declaredTypeRefInAST === null) { - if (!preLinkingPhase) { - if(getterType.isAbstract) { - builtInTypeScope.anyTypeRef - } else { - inferReturnTypeFromReturnStatements(getterDef, builtInTypeScope) - } - } - } else { - getterDef.declaredTypeRefInAST - }; - getterType.typeRef = TypeUtils.copyWithProxies(inferredReturnTypeRef); - } - } - - def protected void setReturnType(TFunction functionType, FunctionDefinition functionDef, - BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - if (!preLinkingPhase) { - val inferredReturnTypeRef = - if (functionDef.declaredReturnTypeRefInAST === null) { - if (!preLinkingPhase) { - inferReturnTypeFromReturnStatements(functionDef, builtInTypeScope) - } - } else { - functionDef.declaredReturnTypeRefInAST - }; - functionType.returnTypeRef = TypeUtils.copyWithProxies(inferredReturnTypeRef); - // note: handling of the return type of async functions not done here, see TypeProcessor#handleAsyncFunctionDeclaration() - } - } - - /** - * Poor man's return type inferencer - */ - // TODO improve that - def protected ParameterizedTypeRef inferReturnTypeFromReturnStatements(FunctionDefinition definition, BuiltInTypeScope builtInTypeScope) { - val hasNonVoidReturn = definition.body!==null && definition.body.hasNonVoidReturn; - if (hasNonVoidReturn) { - return builtInTypeScope.anyTypeRef - } else { - /* - * No Return statements usually implies void as result type for the FunctionDefinition, - * except for those representing arrow functions of the single-expression variety, - * whose result type is heuristically approximated as 'any'. - * - * FIXME that single-expr in an arrow function may well be an invocation to - * a void-method, in which case the 'any' choice is wrong. - */ - if (isSingleExprArrowFunction(definition)) { - return builtInTypeScope.anyTypeRef - } else { - return builtInTypeScope.voidTypeRef - } - } - } - - private def boolean isSingleExprArrowFunction(FunctionDefinition definition) { - switch definition { - ArrowFunction: definition.isSingleExprImplicitReturn - default: false - } - } - - /** - * Poor man's return type inferencer - */ - // TODO improve that - def protected ParameterizedTypeRef inferReturnTypeFromReturnStatements(N4GetterDeclaration definition, BuiltInTypeScope builtInTypeScope) { - val hasNonVoidReturn = definition.body!==null && definition.body.hasNonVoidReturn; - if (hasNonVoidReturn) { - builtInTypeScope.anyTypeRef - } else { - builtInTypeScope.voidTypeRef - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSClassDeclarationTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSClassDeclarationTypesBuilder.java new file mode 100644 index 0000000000..596d72f5c6 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSClassDeclarationTypesBuilder.java @@ -0,0 +1,135 @@ +/** + * 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.typesbuilder; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.N4ClassDefinition; +import org.eclipse.n4js.n4JS.N4ClassExpression; +import org.eclipse.n4js.n4JS.TypeReferenceNode; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.utils.N4JSLanguageUtils; + +/***/ +public class N4JSClassDeclarationTypesBuilder extends N4JSClassifierDeclarationTypesBuilder { + + /***/ + protected boolean relinkTClass(N4ClassDeclaration n4Class, AbstractNamespace target, boolean preLinkingPhase, + int idx) { + if (n4Class.getName() == null) { // may be null due to syntax errors + return false; + } + + TClass tclass = (TClass) target.getTypes().get(idx); + + relinkClassifierAndMembers(tclass, n4Class, preLinkingPhase); + return true; + } + + /***/ + protected TClass createTClass(N4ClassDeclaration n4Class, AbstractNamespace target, boolean preLinkingPhase) { + if (n4Class.getName() == null) { // may be null due to syntax errors + return null; + } + + TClass tclass = createTClass(n4Class); + // modifiers + _n4JSTypesBuilderHelper.setTypeAccessModifier(tclass, n4Class); + _n4JSTypesBuilderHelper.setProvidedByRuntime(tclass, n4Class, preLinkingPhase); + tclass.setDeclaredNonStaticPolyfill(N4JSLanguageUtils.isNonStaticPolyfill(n4Class)); + tclass.setDeclaredStaticPolyfill(N4JSLanguageUtils.isStaticPolyfill(n4Class)); + tclass.setDeclaredCovariantConstructor(_n4JSTypesBuilderHelper.isDeclaredCovariantConstructor(n4Class)); + _n4JSTypeVariableTypesBuilder.addTypeParameters(tclass, n4Class, preLinkingPhase); + + // super types etc + setSuperType(tclass, n4Class, preLinkingPhase); + addImplementedInterfaces(tclass, n4Class, preLinkingPhase); + + // members + addFields(tclass, n4Class, preLinkingPhase); + addMethods(tclass, n4Class, target, preLinkingPhase); + + addGetters(tclass, n4Class, target, preLinkingPhase); + addSetters(tclass, n4Class, target, preLinkingPhase); + + _n4JSTypesBuilderHelper.copyAnnotations(tclass, n4Class, preLinkingPhase); + + // + set "bindings" (derived refs from ast to types and vice versa) + tclass.setAstElement(n4Class); + n4Class.setDefinedType(tclass); + + target.getTypes().add(tclass); + + return tclass; + } + + void createTClass(N4ClassExpression n4Class, AbstractNamespace target, boolean preLinkingPhase) { + TClass tclass = createTClass(n4Class); + + // super types etc + setSuperType(tclass, n4Class, preLinkingPhase); + addImplementedInterfaces(tclass, n4Class, preLinkingPhase); + + // members + addFields(tclass, n4Class, preLinkingPhase); + addMethods(tclass, n4Class, target, preLinkingPhase); + + addGetters(tclass, n4Class, target, preLinkingPhase); + addSetters(tclass, n4Class, target, preLinkingPhase); + + _n4JSTypesBuilderHelper.copyAnnotations(tclass, n4Class, preLinkingPhase); + + // + set "bindings" (derived refs from ast to types and vice versa) + tclass.setAstElement(n4Class); + n4Class.setDefinedType(tclass); + + target.getContainingModule().getInternalTypes().add(tclass); + } + + private TClass createTClass(N4ClassDeclaration classDecl) { + TClass tclass = TypesFactory.eINSTANCE.createTClass(); + tclass.setName(classDecl.getName()); + tclass.setExternal(classDecl.isExternal()); + tclass.setDeclaredAbstract(classDecl.isAbstract()); + tclass.setDeclaredFinal(AnnotationDefinition.FINAL.hasAnnotation(classDecl)); + tclass.setObservable(AnnotationDefinition.OBSERVABLE.hasAnnotation(classDecl)); + tclass.setDeclaredEcmaScript(AnnotationDefinition.ECMASCRIPT.hasAnnotation(classDecl)); + + return tclass; + } + + private TClass createTClass(N4ClassExpression classExpr) { + TClass tclass = TypesFactory.eINSTANCE.createTClass(); + tclass.setName(classExpr.getName()); + return tclass; + } + + private void setSuperType(TClass tclass, N4ClassDefinition classDecl, boolean preLinkingPhase) { + if (!preLinkingPhase) { + TypeReferenceNode scr = classDecl.getSuperClassRef(); + tclass.setSuperClassRef(TypeUtils.copyWithProxies(scr == null ? null : scr.getTypeRefInAST())); + } + } + + private void addImplementedInterfaces(TClass tclass, N4ClassDefinition classDecl, boolean preLinkingPhase) { + if (!preLinkingPhase) { + _n4JSTypesBuilderHelper.addCopyOfReferences(tclass.getImplementedInterfaceRefs(), + toList(map(classDecl.getImplementedInterfaceRefs(), ir -> ir.getTypeRefInAST()))); + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSClassDeclarationTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSClassDeclarationTypesBuilder.xtend deleted file mode 100644 index f99f316f83..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSClassDeclarationTypesBuilder.xtend +++ /dev/null @@ -1,122 +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.typesbuilder - -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.n4JS.N4ClassDeclaration -import org.eclipse.n4js.n4JS.N4ClassDefinition -import org.eclipse.n4js.n4JS.N4ClassExpression -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.TClass -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.utils.N4JSLanguageUtils - -public class N4JSClassDeclarationTypesBuilder extends N4JSClassifierDeclarationTypesBuilder { - - def protected boolean relinkTClass(N4ClassDeclaration n4Class, AbstractNamespace target, boolean preLinkingPhase, int idx) { - if (n4Class.name === null) { // may be null due to syntax errors - return false; - } - - val TClass tclass = target.types.get(idx) as TClass - - tclass.relinkClassifierAndMembers(n4Class, preLinkingPhase); - return true; - } - - def protected TClass createTClass(N4ClassDeclaration n4Class, AbstractNamespace target, boolean preLinkingPhase) { - if (n4Class.name === null) { // may be null due to syntax errors - return null; - } - - val TClass tclass = n4Class.createTClass; - // modifiers - tclass.setTypeAccessModifier(n4Class); - tclass.setProvidedByRuntime(n4Class, preLinkingPhase); - tclass.declaredNonStaticPolyfill = N4JSLanguageUtils.isNonStaticPolyfill(n4Class); - tclass.declaredStaticPolyfill = N4JSLanguageUtils.isStaticPolyfill(n4Class); - tclass.declaredCovariantConstructor = n4Class.isDeclaredCovariantConstructor; - tclass.addTypeParameters(n4Class, preLinkingPhase); - - // super types etc - tclass.setSuperType(n4Class, preLinkingPhase); - tclass.addImplementedInterfaces(n4Class, preLinkingPhase); - - // members - tclass.addFields(n4Class, preLinkingPhase); - tclass.addMethods(n4Class, target, preLinkingPhase); - - tclass.addGetters(n4Class, target, preLinkingPhase); - tclass.addSetters(n4Class, target, preLinkingPhase); - - tclass.copyAnnotations(n4Class, preLinkingPhase); - - // + set "bindings" (derived refs from ast to types and vice versa) - tclass.astElement = n4Class; - n4Class.definedType = tclass; - - target.types += tclass; - - return tclass; - } - - def package createTClass(N4ClassExpression n4Class, AbstractNamespace target, boolean preLinkingPhase) { - val tclass = n4Class.createTClass; - - // super types etc - tclass.setSuperType(n4Class, preLinkingPhase); - tclass.addImplementedInterfaces(n4Class, preLinkingPhase); - - // members - tclass.addFields(n4Class, preLinkingPhase); - tclass.addMethods(n4Class, target, preLinkingPhase); - - tclass.addGetters(n4Class, target, preLinkingPhase); - tclass.addSetters(n4Class, target, preLinkingPhase); - - tclass.copyAnnotations(n4Class, preLinkingPhase); - - // + set "bindings" (derived refs from ast to types and vice versa) - tclass.astElement = n4Class; - n4Class.definedType = tclass; - - target.containingModule.internalTypes += tclass; - } - - def private createTClass(N4ClassDeclaration classDecl) { - val tclass = TypesFactory::eINSTANCE.createTClass(); - tclass.name = classDecl.name; - tclass.external = classDecl.external; - tclass.declaredAbstract = classDecl.abstract; - tclass.declaredFinal = AnnotationDefinition.FINAL.hasAnnotation(classDecl); - tclass.observable = AnnotationDefinition.OBSERVABLE.hasAnnotation(classDecl); - tclass.declaredEcmaScript = AnnotationDefinition.ECMASCRIPT.hasAnnotation(classDecl); - - return tclass; - } - - def private createTClass(N4ClassExpression classExpr) { - val tclass = TypesFactory::eINSTANCE.createTClass(); - tclass.name = classExpr.name; - return tclass; - } - - def private setSuperType(TClass tclass, N4ClassDefinition classDecl, boolean preLinkingPhase) { - if (!preLinkingPhase) - tclass.superClassRef = TypeUtils.copyWithProxies(classDecl.superClassRef?.typeRefInAST); - } - - def private addImplementedInterfaces(TClass tclass, N4ClassDefinition classDecl, boolean preLinkingPhase) { - if (!preLinkingPhase) - addCopyOfReferences(tclass.implementedInterfaceRefs, classDecl.implementedInterfaceRefs.map[typeRefInAST]); - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSClassifierDeclarationTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSClassifierDeclarationTypesBuilder.java new file mode 100644 index 0000000000..66d6e38c4a --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSClassifierDeclarationTypesBuilder.java @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2017 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.typesbuilder; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filterNull; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.n4js.compileTime.CompileTimeEvaluator; +import org.eclipse.n4js.compileTime.CompileTimeValue; +import org.eclipse.n4js.compileTime.CompileTimeValue.ValueValid; +import org.eclipse.n4js.n4JS.Expression; +import org.eclipse.n4js.n4JS.N4ClassifierDeclaration; +import org.eclipse.n4js.n4JS.N4ClassifierDefinition; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.n4JS.N4GetterDeclaration; +import org.eclipse.n4js.n4JS.N4MemberDeclaration; +import org.eclipse.n4js.n4JS.N4MethodDeclaration; +import org.eclipse.n4js.n4JS.N4SetterDeclaration; +import org.eclipse.n4js.n4JS.PropertyNameOwner; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TField; +import org.eclipse.n4js.ts.types.TGetter; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.ts.types.TSetter; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions; +import org.eclipse.n4js.utils.N4JSLanguageUtils; + +import com.google.inject.Inject; + +/** + * Abstract base class for N4JSClassDeclarationTypesBuilder and N4JSInterfaceDeclarationTypesBuilder to provide reusable + * bits and pieces. + */ +abstract class N4JSClassifierDeclarationTypesBuilder { + + @Inject + protected N4JSTypesBuilderHelper _n4JSTypesBuilderHelper; + @Inject + protected N4JSTypeVariableTypesBuilder _n4JSTypeVariableTypesBuilder; + @Inject + protected N4JSFieldTypesBuilder _n4JSFieldTypesBuilder; + @Inject + protected N4JSMethodTypesBuilder _n4JSMethodTypesBuilder; + @Inject + protected N4JSGetterTypesBuilder _n4JSGetterTypesBuilder; + @Inject + protected N4JSSetterTypesBuilder _n4JSSetterTypesBuilder; + @Inject + protected CompileTimeEvaluator compileTimeEvaluator; + + protected void addFields(TClassifier classifier, N4ClassifierDefinition definition, boolean preLinkingPhase) { + Iterable n4Fields = filter(definition.getOwnedMembers(), N4FieldDeclaration.class); + List fields = toList( + filterNull(map(n4Fields, f -> _n4JSFieldTypesBuilder.createField(f, classifier, preLinkingPhase)))); + classifier.getOwnedMembers().addAll(fields); + } + + protected void addMethods(TClassifier classifier, N4ClassifierDefinition definition, AbstractNamespace target, + boolean preLinkingPhase) { + // note: won't include call/construct signatures + Iterable n4Methods = filter(definition.getOwnedMembers(), N4MethodDeclaration.class); + List methods = toList( + filterNull(map(n4Methods, m -> _n4JSMethodTypesBuilder.createMethod(m, target, preLinkingPhase)))); + classifier.getOwnedMembers().addAll(methods); + + N4MethodDeclaration cs = definition.getOwnedCallSignature(); + N4MethodDeclaration css = definition.getOwnedConstructSignature(); + classifier.setCallSignature( + cs == null ? null : _n4JSMethodTypesBuilder.createMethod(cs, target, preLinkingPhase)); + classifier.setConstructSignature( + css == null ? null : _n4JSMethodTypesBuilder.createMethod(css, target, preLinkingPhase)); + } + + protected void addGetters(TClassifier classifier, N4ClassifierDefinition definition, AbstractNamespace target, + boolean preLinkingPhase) { + // create also getters for all non private fields without explicit getter + Iterable n4Getters = filter(definition.getOwnedMembers(), N4GetterDeclaration.class); + List getters = toList(filterNull( + map(n4Getters, g -> _n4JSGetterTypesBuilder.createGetter(g, classifier, target, preLinkingPhase)))); + classifier.getOwnedMembers().addAll(getters); + } + + protected void addSetters(TClassifier classifier, N4ClassifierDefinition definition, AbstractNamespace target, + boolean preLinkingPhase) { + // create also getters for all non private fields without explicit getter + Iterable n4Setters = filter(definition.getOwnedMembers(), N4SetterDeclaration.class); + List setters = toList(filterNull( + map(n4Setters, s -> _n4JSSetterTypesBuilder.createSetter(s, classifier, target, preLinkingPhase)))); + classifier.getOwnedMembers().addAll(setters); + } + + void relinkClassifierAndMembers(TClassifier classifier, N4ClassifierDeclaration declaration, + boolean preLinkingPhase) { + _n4JSTypesBuilderHelper.ensureEqualName(declaration, classifier); + + // members + N4MethodDeclaration astCallSig = declaration.getOwnedCallSignature(); + if (astCallSig != null) { + _n4JSMethodTypesBuilder.relinkMethod(astCallSig, classifier.getCallSignature(), preLinkingPhase); + } + N4MethodDeclaration astConstructSig = declaration.getOwnedConstructSignature(); + if (astConstructSig != null) { + _n4JSMethodTypesBuilder.relinkMethod(astConstructSig, classifier.getConstructSignature(), preLinkingPhase); + } + + // OWNED members + Map memberByName = new HashMap<>(); + for (N4MemberDeclaration member : declaration.getOwnedMembersRaw()) { + PropertyNameOwner pno = (PropertyNameOwner) member; + if (member.getName() != null) { + memberByName.put(member.getName(), member); + } else if (pno.hasComputedPropertyName()) { + Expression expr = pno.getDeclaredName().getExpression(); + if (N4JSLanguageUtils.isProcessedAsCompileTimeExpression(expr)) { + RuleEnvironment G = RuleEnvironmentExtensions.newRuleEnvironment(pno); + CompileTimeValue ctv = compileTimeEvaluator.evaluateCompileTimeExpression(G, expr); + if (ctv.isValid()) { + String ctvName = ((ValueValid) ctv).getValue().toString(); + memberByName.put(ctvName, member); + } + } + } + } + + for (TMember tMember : classifier.getOwnedMembers()) { + N4MemberDeclaration member = memberByName.get(tMember.getName()); + if (tMember instanceof TField && member instanceof N4FieldDeclaration) { + _n4JSFieldTypesBuilder.relinkField((N4FieldDeclaration) member, (TField) tMember, preLinkingPhase); + } + if (tMember instanceof TMethod && member instanceof N4MethodDeclaration) { + N4MethodDeclaration method = (N4MethodDeclaration) member; + if (!method.isConstructSignature() && !method.isCallSignature()) { + _n4JSMethodTypesBuilder.relinkMethod(method, (TMethod) tMember, preLinkingPhase); + } + } + if (tMember instanceof TGetter && member instanceof N4GetterDeclaration) { + _n4JSGetterTypesBuilder.relinkGetter((N4GetterDeclaration) member, (TGetter) tMember, preLinkingPhase); + } + if (tMember instanceof TSetter && member instanceof N4SetterDeclaration) { + _n4JSSetterTypesBuilder.relinkSetter((N4SetterDeclaration) member, (TSetter) tMember, preLinkingPhase); + } + } + + // TODO proxy resolve vs setter invocation? + classifier.setAstElement(declaration); + // setter is ok here + declaration.setDefinedType(classifier); + } + +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSClassifierDeclarationTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSClassifierDeclarationTypesBuilder.xtend deleted file mode 100644 index 1deeadd0a1..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSClassifierDeclarationTypesBuilder.xtend +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright (c) 2017 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.typesbuilder - -import com.google.inject.Inject -import java.util.HashMap -import java.util.Map -import org.eclipse.n4js.compileTime.CompileTimeEvaluator -import org.eclipse.n4js.compileTime.CompileTimeValue.ValueValid -import org.eclipse.n4js.n4JS.N4ClassifierDeclaration -import org.eclipse.n4js.n4JS.N4ClassifierDefinition -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.n4JS.N4GetterDeclaration -import org.eclipse.n4js.n4JS.N4MemberDeclaration -import org.eclipse.n4js.n4JS.N4MethodDeclaration -import org.eclipse.n4js.n4JS.N4SetterDeclaration -import org.eclipse.n4js.n4JS.PropertyNameOwner -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TField -import org.eclipse.n4js.ts.types.TGetter -import org.eclipse.n4js.ts.types.TMethod -import org.eclipse.n4js.ts.types.TSetter -import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions -import org.eclipse.n4js.utils.N4JSLanguageUtils - -/** - * Abstract base class for N4JSClassDeclarationTypesBuilder and N4JSInterfaceDeclarationTypesBuilder - * to provide reusable bits and pieces. - */ -package abstract class N4JSClassifierDeclarationTypesBuilder { - - @Inject protected extension N4JSTypesBuilderHelper - @Inject protected extension N4JSTypeVariableTypesBuilder - @Inject protected extension N4JSFieldTypesBuilder - @Inject protected extension N4JSMethodTypesBuilder - @Inject protected extension N4JSGetterTypesBuilder - @Inject protected extension N4JSSetterTypesBuilder - @Inject protected CompileTimeEvaluator compileTimeEvaluator; - - def protected void addFields(TClassifier classifier, N4ClassifierDefinition definition, boolean preLinkingPhase) { - val n4Fields = definition.ownedMembers.filter(N4FieldDeclaration); - val fields = n4Fields.map[createField(classifier, preLinkingPhase)].filterNull - classifier.ownedMembers.addAll(fields); - } - - def protected void addMethods(TClassifier classifier, N4ClassifierDefinition definition, AbstractNamespace target, boolean preLinkingPhase) { - val n4Methods = definition.ownedMembers.filter(N4MethodDeclaration); // note: won't include call/construct signatures - val methods = n4Methods.map[createMethod(target, preLinkingPhase)].filterNull; - classifier.ownedMembers.addAll(methods); - classifier.callSignature = definition.ownedCallSignature?.createMethod(target, preLinkingPhase); - classifier.constructSignature = definition.ownedConstructSignature?.createMethod(target, preLinkingPhase); - } - - def protected void addGetters(TClassifier classifier, N4ClassifierDefinition definition, AbstractNamespace target, boolean preLinkingPhase) { - // create also getters for all non private fields without explicit getter - val n4Getters = definition.ownedMembers.filter(N4GetterDeclaration) - val getters = n4Getters.map[createGetter(classifier, target, preLinkingPhase)].filterNull - classifier.ownedMembers.addAll(getters); - } - - def protected void addSetters(TClassifier classifier, N4ClassifierDefinition definition, AbstractNamespace target, boolean preLinkingPhase) { - // create also getters for all non private fields without explicit getter - val n4Setters = definition.ownedMembers.filter(N4SetterDeclaration) - val setters = n4Setters.map[createSetter(classifier, target, preLinkingPhase)].filterNull - classifier.ownedMembers.addAll(setters); - } - - def package void relinkClassifierAndMembers(TClassifier classifier, N4ClassifierDeclaration declaration, boolean preLinkingPhase) { - ensureEqualName(declaration, classifier); - - // members - val astCallSig = declaration.ownedCallSignature; - if (astCallSig !== null ) { - relinkMethod(astCallSig, classifier.callSignature, preLinkingPhase) - } - val astConstructSig = declaration.ownedConstructSignature; - if (astConstructSig !== null) { - relinkMethod(astConstructSig, classifier.constructSignature, preLinkingPhase) - } - - // OWNED members - val Map memberByName = new HashMap(); - for (member : declaration.ownedMembersRaw) { - val pno = member as PropertyNameOwner; - if (member.name !== null) { - memberByName.put(member.name, member); - } else if (pno.hasComputedPropertyName) { - val expr = pno.declaredName.expression; - if (N4JSLanguageUtils.isProcessedAsCompileTimeExpression(expr)) { - val G = RuleEnvironmentExtensions.newRuleEnvironment(pno); - val ctv = compileTimeEvaluator.evaluateCompileTimeExpression(G, expr); - if (ctv.valid) { - val ctvName = (ctv as ValueValid).value.toString; - memberByName.put(ctvName, member); - } - } - } - } - - var idx = 0; - for (tMember : classifier.ownedMembers) { - val member = memberByName.get(tMember.name); - if (tMember instanceof TField && member instanceof N4FieldDeclaration) { - relinkField(member as N4FieldDeclaration, tMember as TField, preLinkingPhase); - } - if (tMember instanceof TMethod && member instanceof N4MethodDeclaration) { - val method = member as N4MethodDeclaration; - if (!method.isConstructSignature && !method.isCallSignature) { - relinkMethod(method, tMember as TMethod, preLinkingPhase); - } - } - if (tMember instanceof TGetter && member instanceof N4GetterDeclaration) { - relinkGetter(member as N4GetterDeclaration, tMember as TGetter, preLinkingPhase); - } - if (tMember instanceof TSetter && member instanceof N4SetterDeclaration) { - relinkSetter(member as N4SetterDeclaration, tMember as TSetter, preLinkingPhase); - } - idx++; - } - - // TODO proxy resolve vs setter invocation? - classifier.astElement = declaration; - // setter is ok here - declaration.definedType = classifier; - } - -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSEnumDeclarationTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSEnumDeclarationTypesBuilder.java new file mode 100644 index 0000000000..cacba812ee --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSEnumDeclarationTypesBuilder.java @@ -0,0 +1,145 @@ +/** + * 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.typesbuilder; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filterNull; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.fold; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toSet; + +import java.math.BigDecimal; +import java.util.Set; + +import org.eclipse.n4js.n4JS.N4EnumDeclaration; +import org.eclipse.n4js.n4JS.N4EnumLiteral; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.TEnum; +import org.eclipse.n4js.ts.types.TEnumLiteral; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind; + +import com.google.inject.Inject; + +@SuppressWarnings("javadoc") +public class N4JSEnumDeclarationTypesBuilder { + + @Inject + N4JSTypesBuilderHelper _n4JSTypesBuilderHelper; + + boolean relinkTEnum(N4EnumDeclaration n4Enum, AbstractNamespace target, + @SuppressWarnings("unused") boolean preLinkingPhase, int idx) { + + if (n4Enum.getName() == null) { + return false; + } + + TEnum enumType = (TEnum) target.getTypes().get(idx); + _n4JSTypesBuilderHelper.ensureEqualName(n4Enum, enumType); + + relinkTEnumLiterals(n4Enum, enumType); + + enumType.setAstElement(n4Enum); + n4Enum.setDefinedType(enumType); + return true; + } + + private int relinkTEnumLiterals(N4EnumDeclaration n4Enum, TEnum tEnum) { + return fold(n4Enum.getLiterals(), 0, (idx, n4EnumLit) -> { + if (relinkTEnumLiteral(n4EnumLit, tEnum, idx)) { + return idx + 1; + } + return idx; + }); + } + + private boolean relinkTEnumLiteral(N4EnumLiteral n4EnumLit, TEnum tEnum, int idx) { + TEnumLiteral tEnumLit = tEnum.getLiterals().get(idx); + _n4JSTypesBuilderHelper.ensureEqualName(n4EnumLit, tEnumLit); + tEnumLit.setAstElement(n4EnumLit); + n4EnumLit.setDefinedLiteral(tEnumLit); + return true; + } + + protected TEnum createTEnum(N4EnumDeclaration n4Enum, AbstractNamespace target, boolean preLinkingPhase) { + if (n4Enum.getName() == null) { + return null; + } + + TEnum enumType = createTEnum(n4Enum); + _n4JSTypesBuilderHelper.setTypeAccessModifier(enumType, n4Enum); + _n4JSTypesBuilderHelper.setProvidedByRuntime(enumType, n4Enum, preLinkingPhase); + addLiterals(enumType, n4Enum); + _n4JSTypesBuilderHelper.copyAnnotations(enumType, n4Enum, preLinkingPhase); + + computeDefaultValues(enumType, n4Enum); + + enumType.setAstElement(n4Enum); + n4Enum.setDefinedType(enumType); + + target.getTypes().add(enumType); + + return enumType; + } + + private TEnum createTEnum(N4EnumDeclaration n4Enum) { + TEnum enumType = TypesFactory.eINSTANCE.createTEnum(); + enumType.setName(n4Enum.getName()); + enumType.setExternal(n4Enum.isExternal()); + return enumType; + } + + private void addLiterals(TEnum enumType, N4EnumDeclaration n4Enum) { + enumType.getLiterals().addAll(toList( + map(filter(n4Enum.getLiterals(), N4EnumLiteral.class), el -> createEnumLiteral(el)))); + } + + private TEnumLiteral createEnumLiteral(N4EnumLiteral n4Literal) { + Object value = N4JSLanguageUtils.getEnumLiteralValue(n4Literal); + + TEnumLiteral tLiteral = TypesFactory.eINSTANCE.createTEnumLiteral(); + tLiteral.setName(n4Literal.getName()); + tLiteral.setValueString((value instanceof String) ? (String) value : null); + tLiteral.setValueNumber((value instanceof BigDecimal) ? (BigDecimal) value : null); + tLiteral.setAstElement(n4Literal); + n4Literal.setDefinedLiteral(tLiteral); + return tLiteral; + } + + private void computeDefaultValues(TEnum enumType, N4EnumDeclaration n4Enum) { + EnumKind enumKind = N4JSLanguageUtils.getEnumKind(n4Enum); + if (enumKind == EnumKind.NumberBased) { + // @NumberBased enums + Set usedNumbers = toSet(filterNull(map(enumType.getLiterals(), l -> l.getValueNumber()))); + BigDecimal next = BigDecimal.ONE.negate(); + for (TEnumLiteral tLiteral : enumType.getLiterals()) { + if (tLiteral.getValueNumber() != null) { + next = tLiteral.getValueNumber(); + } else { + do { + next = next.add(BigDecimal.ONE); + } while (usedNumbers.contains(next)); + tLiteral.setValueNumber(next); + usedNumbers.add(next); + } + } + } else { + // ordinary and @StringBased enums + for (TEnumLiteral lit : enumType.getLiterals()) { + if (lit.getValueString() == null) { + lit.setValueString(lit.getName()); + } + } + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSEnumDeclarationTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSEnumDeclarationTypesBuilder.xtend deleted file mode 100644 index 0f1fbfaeb1..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSEnumDeclarationTypesBuilder.xtend +++ /dev/null @@ -1,126 +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.typesbuilder - -import com.google.inject.Inject -import java.math.BigDecimal -import org.eclipse.n4js.n4JS.N4EnumDeclaration -import org.eclipse.n4js.n4JS.N4EnumLiteral -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.TEnum -import org.eclipse.n4js.ts.types.TEnumLiteral -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind - -public class N4JSEnumDeclarationTypesBuilder { - - @Inject extension N4JSTypesBuilderHelper - - def package boolean relinkTEnum(N4EnumDeclaration n4Enum, AbstractNamespace target, boolean preLinkingPhase, int idx) { - if (n4Enum.name === null) { - return false; - } - - val TEnum enumType = target.types.get(idx) as TEnum - ensureEqualName(n4Enum, enumType); - - relinkTEnumLiterals(n4Enum, enumType, preLinkingPhase); - - enumType.astElement = n4Enum - n4Enum.definedType = enumType - return true; - } - - def private int relinkTEnumLiterals(N4EnumDeclaration n4Enum, TEnum tEnum, boolean preLinkingPhase) { - return n4Enum.literals.fold(0) [ idx, n4EnumLit | - if (relinkTEnumLiteral(n4EnumLit, tEnum, preLinkingPhase, idx)) { - return idx + 1; - } - return idx; - ] - } - - def private boolean relinkTEnumLiteral(N4EnumLiteral n4EnumLit, TEnum tEnum, boolean preLinkingPhase, int idx) { - val tEnumLit = tEnum.literals.get(idx); - ensureEqualName(n4EnumLit, tEnumLit); - tEnumLit.astElement = n4EnumLit; - n4EnumLit.definedLiteral = tEnumLit; - return true; - } - - def protected TEnum createTEnum(N4EnumDeclaration n4Enum, AbstractNamespace target, boolean preLinkingPhase) { - if (n4Enum.name === null) { - return null; - } - - val enumType = n4Enum.createTEnum - enumType.setTypeAccessModifier(n4Enum) - enumType.setProvidedByRuntime(n4Enum, preLinkingPhase) - enumType.addLiterals(n4Enum, preLinkingPhase) - enumType.copyAnnotations(n4Enum, preLinkingPhase) - - computeDefaultValues(enumType, n4Enum); - - enumType.astElement = n4Enum - n4Enum.definedType = enumType - - target.types += enumType - - return enumType; - } - - def private TEnum createTEnum(N4EnumDeclaration n4Enum) { - val enumType = TypesFactory::eINSTANCE.createTEnum(); - enumType.name = n4Enum.name; - enumType.external = n4Enum.external; - enumType - } - - def private void addLiterals(TEnum enumType, N4EnumDeclaration n4Enum, boolean preLinkingPhase) { - enumType.literals.addAll(n4Enum.literals.filter(N4EnumLiteral).map[createEnumLiteral(preLinkingPhase)]); - } - - def private TEnumLiteral createEnumLiteral(N4EnumLiteral n4Literal, boolean preLinkingPhase) { - val value = N4JSLanguageUtils.getEnumLiteralValue(n4Literal); - - val tLiteral = TypesFactory::eINSTANCE.createTEnumLiteral(); - tLiteral.name = n4Literal.name; - tLiteral.valueString = if (value instanceof String) value; - tLiteral.valueNumber = if (value instanceof BigDecimal) value; - tLiteral.astElement = n4Literal; - n4Literal.definedLiteral = tLiteral - return tLiteral; - } - - def private void computeDefaultValues(TEnum enumType, N4EnumDeclaration n4Enum) { - val enumKind = N4JSLanguageUtils.getEnumKind(n4Enum); - if (enumKind === EnumKind.NumberBased) { - // @NumberBased enums - val usedNumbers = enumType.literals.map[valueNumber].filterNull.toSet; - var next = BigDecimal.ONE.negate(); - for (tLiteral : enumType.literals) { - if (tLiteral.valueNumber !== null) { - next = tLiteral.valueNumber; - } else { - do { - next = next.add(BigDecimal.ONE); - } while(usedNumbers.contains(next)); - tLiteral.valueNumber = next; - usedNumbers += next; - } - } - } else { - // ordinary and @StringBased enums - enumType.literals.filter[valueString === null].forEach[valueString = name]; - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSExportDefinitionTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSExportDefinitionTypesBuilder.java new file mode 100644 index 0000000000..a9ae6a4659 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSExportDefinitionTypesBuilder.java @@ -0,0 +1,144 @@ +/** + * Copyright (c) 2022 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.typesbuilder; + +import java.util.Objects; + +import org.eclipse.emf.ecore.InternalEObject; +import org.eclipse.n4js.N4JSLanguageConstants; +import org.eclipse.n4js.n4JS.ExportDeclaration; +import org.eclipse.n4js.n4JS.ExportableElement; +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.n4JS.NamedExportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceExportSpecifier; +import org.eclipse.n4js.n4JS.TypeDefiningElement; +import org.eclipse.n4js.n4JS.VariableStatement; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.ElementExportDefinition; +import org.eclipse.n4js.ts.types.ExportDefinition; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.ModuleExportDefinition; +import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType; +import org.eclipse.n4js.ts.types.TExportableElement; +import org.eclipse.n4js.ts.types.TExportingElement; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.xtext.EcoreUtil2; + +import com.google.inject.Inject; + +/** + * Does not create new types but instead creates {@link ExportDefinition}s for {@link ExportDeclaration}s. + */ +class N4JSExportDefinitionTypesBuilder { + + @Inject + N4JSTypesBuilderHelper _n4JSTypesBuilderHelper; + + // relinking export definitions not required + // (see N4JSTypesBuilder#relinkTypes(EObject, AbstractNamespace, boolean, RelinkIndices) for details) + + void createExportDefinition(ExportDeclaration exportDecl, AbstractNamespace target, boolean preLinkingPhase) { + ExportableElement directlyExportedElem = exportDecl.getExportedElement(); + if (directlyExportedElem != null) { + createExportDefinitionForDirectlyExportedElement(directlyExportedElem, target, preLinkingPhase); + } else if (exportDecl.getNamespaceExport() != null) { + NamespaceExportSpecifier exportSpec = exportDecl.getNamespaceExport(); + TModule exportedModuleProxy = (TModule) exportDecl.eGet(N4JSPackage.eINSTANCE.getModuleRef_Module(), false); + if (exportedModuleProxy != null) { + TModule exportedModuleProxyCopy = EcoreUtil2.cloneWithProxies(exportedModuleProxy); + String alias = exportSpec.getAlias(); + if (alias != null) { + ModuleNamespaceVirtualType mnvt = _n4JSTypesBuilderHelper.addNewModuleNamespaceVirtualType( + target.getContainingModule(), alias, exportedModuleProxy, false, exportSpec); + addElementExportDefinition(target, alias, mnvt); + } else { + addModuleExportDefinition(target, exportedModuleProxyCopy); + } + } + } else { + for (NamedExportSpecifier exportSpec : exportDecl.getNamedExports()) { + IdentifierRef idRef = exportSpec.getExportedElement(); + if (idRef != null) { + IdentifiableElement expElemProxy = (IdentifiableElement) idRef + .eGet(N4JSPackage.eINSTANCE.getIdentifierRef_Id(), false); + if (expElemProxy != null) { + TExportableElement expElemProxyCpy = TypesFactory.eINSTANCE.createTExportableElement(); + ((InternalEObject) expElemProxyCpy).eSetProxyURI(((InternalEObject) expElemProxy).eProxyURI()); + var expName = exportSpec.getAlias(); + if (expName == null) { + // we do not use the name of the actually exported element here, because ... + // 1) we only have a proxy to the exported element (i.e. expElemProxy) anyway and are not + // allowed + // to trigger proxy resolution in the types builder, so we cannot retrieve its name; + // 2) the exported element might have been imported from another file under an alias and in + // that + // case the import specifier's alias will be the default exported name, not the element's + // name. + expName = idRef.getIdAsText(); + } + addElementExportDefinition(target, expName, expElemProxyCpy); + } + } + } + } + } + + void createExportDefinitionForDirectlyExportedElement(ExportableElement directlyExportedElem, + AbstractNamespace target, boolean preLinkingPhase) { + if (directlyExportedElem instanceof VariableStatement) { + // variable statements inherit from ExportableElement, but they do not actually represent + // the element being exported (instead, the variable declarations represent those elements) + // --> ignore them here + return; + } + TExportableElement tDirectlyExportedElem = getExportedTypesModelElement(directlyExportedElem); + if (tDirectlyExportedElem == null) { + return; // broken AST (e.g. class declaration with missing name) + } + createExportDefinitionForDirectlyExportedElement(tDirectlyExportedElem, + directlyExportedElem.getDirectlyExportedName(), target, preLinkingPhase); + } + + void createExportDefinitionForDirectlyExportedElement(TExportableElement tDirectlyExportedElem, String exportedName, + AbstractNamespace target, @SuppressWarnings("unused") boolean preLinkingPhase) { + tDirectlyExportedElem.setDirectlyExported(true); + tDirectlyExportedElem + .setDirectlyExportedAsDefault(N4JSLanguageConstants.EXPORT_DEFAULT_NAME.equals(exportedName)); + addElementExportDefinition(target, exportedName, tDirectlyExportedElem); + } + + private void addModuleExportDefinition(TExportingElement exportingElem, TModule exportedModule) { + ModuleExportDefinition expDef = TypesFactory.eINSTANCE.createModuleExportDefinition(); + expDef.setExportedModule(exportedModule); + exportingElem.getExportDefinitions().add(expDef); + } + + private ExportDefinition addElementExportDefinition(TExportingElement exportingElem, String exportedName, + TExportableElement exportedElem) { + Objects.requireNonNull(exportingElem); + Objects.requireNonNull(exportedName); + Objects.requireNonNull(exportedElem); + ElementExportDefinition expDef = TypesFactory.eINSTANCE.createElementExportDefinition(); + expDef.setExportedName(exportedName); + expDef.setExportedElement(exportedElem); + exportingElem.getExportDefinitions().add(expDef); + return expDef; + } + + private TExportableElement getExportedTypesModelElement(ExportableElement n4ExportableElem) { + if (n4ExportableElem instanceof TypeDefiningElement) { + return ((TypeDefiningElement) n4ExportableElem).getDefinedType(); + } + return null; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSExportDefinitionTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSExportDefinitionTypesBuilder.xtend deleted file mode 100644 index 08ea295a51..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSExportDefinitionTypesBuilder.xtend +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright (c) 2022 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.typesbuilder - -import com.google.inject.Inject -import java.util.Objects -import org.eclipse.emf.ecore.InternalEObject -import org.eclipse.n4js.N4JSLanguageConstants -import org.eclipse.n4js.n4JS.ExportDeclaration -import org.eclipse.n4js.n4JS.ExportableElement -import org.eclipse.n4js.n4JS.N4JSPackage -import org.eclipse.n4js.n4JS.NamedExportSpecifier -import org.eclipse.n4js.n4JS.TypeDefiningElement -import org.eclipse.n4js.n4JS.VariableStatement -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.ExportDefinition -import org.eclipse.n4js.ts.types.IdentifiableElement -import org.eclipse.n4js.ts.types.TExportableElement -import org.eclipse.n4js.ts.types.TExportingElement -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.xtext.EcoreUtil2 - -/** - * Does not create new types but instead creates {@link ExportDefinition}s for {@link ExportDeclaration}s. - */ -class N4JSExportDefinitionTypesBuilder { - - @Inject extension N4JSTypesBuilderHelper - - // relinking export definitions not required - // (see N4JSTypesBuilder#relinkTypes(EObject, AbstractNamespace, boolean, RelinkIndices) for details) - - def package void createExportDefinition(ExportDeclaration exportDecl, AbstractNamespace target, boolean preLinkingPhase) { - val directlyExportedElem = exportDecl.exportedElement; - if (directlyExportedElem !== null) { - createExportDefinitionForDirectlyExportedElement(directlyExportedElem, target, preLinkingPhase); - } else if (exportDecl.namespaceExport !== null) { - val exportSpec = exportDecl.namespaceExport; - val exportedModuleProxy = exportDecl.eGet(N4JSPackage.eINSTANCE.moduleRef_Module, false) as TModule; - if (exportedModuleProxy !== null) { - val exportedModuleProxyCopy = EcoreUtil2.cloneWithProxies(exportedModuleProxy); - val alias = exportSpec.alias; - if (alias !== null) { - val mnvt = target.containingModule.addNewModuleNamespaceVirtualType(alias, exportedModuleProxy, false, exportSpec); - addElementExportDefinition(target, alias, mnvt); - } else { - addModuleExportDefinition(target, exportedModuleProxyCopy); - } - } - } else { - for (NamedExportSpecifier exportSpec : exportDecl.namedExports) { - val idRef = exportSpec.exportedElement; - if (idRef !== null) { - val expElemProxy = idRef.eGet(N4JSPackage.eINSTANCE.identifierRef_Id, false) as IdentifiableElement; - if (expElemProxy !== null) { - val expElemProxyCpy = TypesFactory.eINSTANCE.createTExportableElement(); - (expElemProxyCpy as InternalEObject).eSetProxyURI((expElemProxy as InternalEObject).eProxyURI()); - var expName = exportSpec.alias; - if (expName === null) { - // we do not use the name of the actually exported element here, because ... - // 1) we only have a proxy to the exported element (i.e. expElemProxy) anyway and are not allowed - // to trigger proxy resolution in the types builder, so we cannot retrieve its name; - // 2) the exported element might have been imported from another file under an alias and in that - // case the import specifier's alias will be the default exported name, not the element's name. - expName = idRef.idAsText; - } - addElementExportDefinition(target, expName, expElemProxyCpy); - } - } - } - } - } - - def package void createExportDefinitionForDirectlyExportedElement(ExportableElement directlyExportedElem, AbstractNamespace target, boolean preLinkingPhase) { - if (directlyExportedElem instanceof VariableStatement) { - // variable statements inherit from ExportableElement, but they do not actually represent - // the element being exported (instead, the variable declarations represent those elements) - // --> ignore them here - return; - } - val tDirectlyExportedElem = getExportedTypesModelElement(directlyExportedElem); - if (tDirectlyExportedElem === null) { - return; // broken AST (e.g. class declaration with missing name) - } - createExportDefinitionForDirectlyExportedElement(tDirectlyExportedElem, directlyExportedElem.directlyExportedName, target, preLinkingPhase); - } - - def package void createExportDefinitionForDirectlyExportedElement(TExportableElement tDirectlyExportedElem, String exportedName, AbstractNamespace target, boolean preLinkingPhase) { - tDirectlyExportedElem.directlyExported = true; - tDirectlyExportedElem.directlyExportedAsDefault = exportedName == N4JSLanguageConstants.EXPORT_DEFAULT_NAME; - addElementExportDefinition(target, exportedName, tDirectlyExportedElem); - } - - def private void addModuleExportDefinition(TExportingElement exportingElem, TModule exportedModule) { - val expDef = TypesFactory.eINSTANCE.createModuleExportDefinition(); - expDef.exportedModule = exportedModule; - exportingElem.exportDefinitions += expDef; - } - - def private ExportDefinition addElementExportDefinition(TExportingElement exportingElem, String exportedName, TExportableElement exportedElem) { - Objects.requireNonNull(exportingElem); - Objects.requireNonNull(exportedName); - Objects.requireNonNull(exportedElem); - val expDef = TypesFactory.eINSTANCE.createElementExportDefinition(); - expDef.exportedName = exportedName; - expDef.exportedElement = exportedElem; - exportingElem.exportDefinitions += expDef; - return expDef; - } - - def private TExportableElement getExportedTypesModelElement(ExportableElement n4ExportableElem) { - return switch n4ExportableElem { - TypeDefiningElement: n4ExportableElem.definedType - }; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFieldTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFieldTypesBuilder.java new file mode 100644 index 0000000000..d4755b5dac --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFieldTypesBuilder.java @@ -0,0 +1,90 @@ +/** + * 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.typesbuilder; + +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.n4JS.N4FieldDeclaration; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TField; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.types.utils.TypeUtils; + +import com.google.inject.Inject; + +class N4JSFieldTypesBuilder { + + @Inject + N4JSTypesBuilderHelper _n4JSTypesBuilderHelper; + + boolean canCreate(N4FieldDeclaration n4Field) { + return n4Field.getName() != null || n4Field.hasComputedPropertyName(); + } + + boolean relinkField(N4FieldDeclaration n4Field, TField tField, + @SuppressWarnings("unused") boolean preLinkingPhase) { + if (!canCreate(n4Field)) { + return false; + } + + _n4JSTypesBuilderHelper.ensureEqualName(n4Field, tField); + tField.setAstElement(n4Field); + n4Field.setDefinedField(tField); + + return true; + } + + TField createField(N4FieldDeclaration n4Field, @SuppressWarnings("unused") TClassifier classifierType, + boolean preLinkingPhase) { + if (!canCreate(n4Field)) { + return null; + } + + TField field = TypesFactory.eINSTANCE.createTField(); + _n4JSTypesBuilderHelper.setMemberName(field, n4Field); + field.setConst(n4Field.isConst()); + field.setDeclaredStatic(n4Field.isDeclaredStatic()); + field.setDeclaredFinal(n4Field.isDeclaredFinal()); + field.setOptional(n4Field.isDeclaredOptional()); + field.setDeclaredOverride(AnnotationDefinition.OVERRIDE.hasAnnotation(n4Field)); + + boolean providesInitializer = AnnotationDefinition.PROVIDES_INITIALZER.hasAnnotation(n4Field); + field.setHasExpression(n4Field.getExpression() != null || providesInitializer); + + _n4JSTypesBuilderHelper.copyAnnotations(field, n4Field, preLinkingPhase); + + setMemberAccessModifier(field, n4Field); + setFieldType(field, n4Field, preLinkingPhase); + + field.setAstElement(n4Field); + n4Field.setDefinedField(field); + + return field; + } + + private void setFieldType(TField field, N4FieldDeclaration n4Field, boolean preLinkingPhase) { + if (!preLinkingPhase) { + if (n4Field.getDeclaredTypeRefInAST() != null) { + // type of field was declared explicitly + field.setTypeRef(TypeUtils.copyWithProxies(n4Field.getDeclaredTypeRefInAST())); + } else { + // in all other cases: + // leave it to the TypingASTWalker to infer the type (e.g. from the initializer expression, if given) + field.setTypeRef(TypeUtils.createDeferredTypeRef()); + } + } + } + + private void setMemberAccessModifier(TField fieldType, N4FieldDeclaration n4Field) { + _n4JSTypesBuilderHelper.setMemberAccessModifier( + (modifier) -> fieldType.setDeclaredMemberAccessModifier(modifier), + n4Field.getDeclaredModifiers(), n4Field.getAnnotations()); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFieldTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFieldTypesBuilder.xtend deleted file mode 100644 index fd4f0dbc0e..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFieldTypesBuilder.xtend +++ /dev/null @@ -1,87 +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.typesbuilder - -import com.google.inject.Inject -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.n4JS.N4FieldDeclaration -import org.eclipse.n4js.ts.types.MemberAccessModifier -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TField -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.types.utils.TypeUtils - -package class N4JSFieldTypesBuilder { - - @Inject extension N4JSTypesBuilderHelper - - def boolean canCreate(N4FieldDeclaration n4Field) { - return n4Field.name !== null || n4Field.hasComputedPropertyName; - } - - def package boolean relinkField(N4FieldDeclaration n4Field, TField tField, boolean preLinkingPhase) { - if (!canCreate(n4Field)) { - return false - } - - ensureEqualName(n4Field, tField); - tField.astElement = n4Field; - n4Field.definedField = tField - - return true; - } - - def package TField createField(N4FieldDeclaration n4Field, TClassifier classifierType, boolean preLinkingPhase) { - if (!canCreate(n4Field)) { - return null; - } - - val field = TypesFactory::eINSTANCE.createTField(); - field.setMemberName(n4Field); - field.const = n4Field.const - field.declaredStatic = n4Field.declaredStatic; - field.declaredFinal = n4Field.declaredFinal; - field.optional = n4Field.declaredOptional; - field.declaredOverride = AnnotationDefinition.OVERRIDE.hasAnnotation(n4Field); - - val providesInitializer = AnnotationDefinition.PROVIDES_INITIALZER.hasAnnotation(n4Field); - field.hasExpression = n4Field.expression!==null || providesInitializer; - - field.copyAnnotations(n4Field, preLinkingPhase) - - field.setMemberAccessModifier(n4Field) - field.setFieldType(n4Field, preLinkingPhase) - - field.astElement = n4Field; - n4Field.definedField = field - - return field; - } - - def private void setFieldType(TField field, N4FieldDeclaration n4Field, boolean preLinkingPhase) { - if (!preLinkingPhase) { - if(n4Field.declaredTypeRefInAST!==null) { - // type of field was declared explicitly - field.typeRef = TypeUtils.copyWithProxies(n4Field.declaredTypeRefInAST) - } - else { - // in all other cases: - // leave it to the TypingASTWalker to infer the type (e.g. from the initializer expression, if given) - field.typeRef = TypeUtils.createDeferredTypeRef; - } - } - } - - def private void setMemberAccessModifier(TField fieldType, N4FieldDeclaration n4Field) { - setMemberAccessModifier([MemberAccessModifier modifier | fieldType.declaredMemberAccessModifier = modifier], - n4Field.declaredModifiers, n4Field.annotations) - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFormalParameterTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFormalParameterTypesBuilder.java new file mode 100644 index 0000000000..04f90594d5 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFormalParameterTypesBuilder.java @@ -0,0 +1,97 @@ +/** + * 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.typesbuilder; + +import org.eclipse.n4js.n4JS.FormalParameter; +import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.TFormalParameter; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.types.utils.TypeUtils; + +import com.google.inject.Inject; + +class N4JSFormalParameterTypesBuilder { + + @Inject + N4JSTypesBuilderHelper _n4JSTypesBuilderHelper; + + boolean relinkFormalParameter(FormalParameter astFormalParameter, TFunction functionType, + BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase, int idx) { + TFormalParameter formalParameterType = functionType.getFpars().get(idx); + _n4JSTypesBuilderHelper.ensureEqualName(astFormalParameter, formalParameterType); + + formalParameterType.setAstElement(astFormalParameter); + astFormalParameter.setDefinedVariable(formalParameterType); + setFormalParameterType(formalParameterType, astFormalParameter, null, builtInTypeScope, preLinkingPhase); + + return true; + } + + TFormalParameter createFormalParameter(FormalParameter n4FormalParameter, BuiltInTypeScope builtInTypeScope, + boolean preLinkingPhase) { + return createFormalParameter(n4FormalParameter, null, builtInTypeScope, preLinkingPhase); + } + + /** + * Creates a TFormalParameter for the given FormalParameter from the AST. + * + * @param defaultTypeRef + * will be used in case there is no declared type for the formal parameter; this may be null + * and in this case any will be the formal parameter's actual type. + */ + TFormalParameter createFormalParameter(FormalParameter astFormalParameter, TypeRef defaultTypeRef, + BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { + // note: we also build an fpar if astFormalParameter.name===null (otherwise the AST and types model + // would have different number of formal parameters in case of a broken AST, messing up indices, etc.) + TFormalParameter formalParameterType = TypesFactory.eINSTANCE.createTFormalParameter(); + formalParameterType.setName(astFormalParameter.getName()); + formalParameterType.setVariadic(astFormalParameter.isVariadic()); + formalParameterType.setAstInitializer(null); + formalParameterType.setHasInitializerAssignment(astFormalParameter.isHasInitializerAssignment()); + setFormalParameterType(formalParameterType, astFormalParameter, defaultTypeRef, builtInTypeScope, + preLinkingPhase); + + _n4JSTypesBuilderHelper.copyAnnotations(formalParameterType, astFormalParameter, preLinkingPhase); + + formalParameterType.setAstElement(astFormalParameter); + astFormalParameter.setDefinedVariable(formalParameterType); + + return formalParameterType; + } + + /** + * @param formalParameterType + * the type system related parameter type to be set + * @param astFormalParameter + * the AST related parameter which is to be copied to the former + */ + private void setFormalParameterType(TFormalParameter formalParameterType, FormalParameter astFormalParameter, + TypeRef defaultTypeRef, BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { + if (!preLinkingPhase) { + TypeRef copy = TypeUtils.copyWithProxies(astFormalParameter.getDeclaredTypeRefInAST()); + formalParameterType.setTypeRef(copy != null ? copy + : getDefaultParameterType(defaultTypeRef, astFormalParameter, builtInTypeScope)); + } + } + + private TypeRef getDefaultParameterType(TypeRef defaultTypeRef, + FormalParameter astFormalParameter, BuiltInTypeScope builtInTypeScope) { + if (astFormalParameter.getInitializer() != null) { + return TypeUtils.createDeferredTypeRef(); + } else if (defaultTypeRef == null) { + return builtInTypeScope.getAnyTypeRef(); + } else { + return TypeUtils.copy(defaultTypeRef); + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFormalParameterTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFormalParameterTypesBuilder.xtend deleted file mode 100644 index 939b88c359..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFormalParameterTypesBuilder.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.typesbuilder - -import com.google.inject.Inject -import org.eclipse.n4js.n4JS.FormalParameter -import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.TFormalParameter -import org.eclipse.n4js.ts.types.TFunction -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.types.utils.TypeUtils - -package class N4JSFormalParameterTypesBuilder { - - @Inject extension N4JSTypesBuilderHelper - - def package boolean relinkFormalParameter(FormalParameter astFormalParameter, TFunction functionType, BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase, int idx) { - val formalParameterType = functionType.fpars.get(idx); - ensureEqualName(astFormalParameter, formalParameterType); - - formalParameterType.astElement = astFormalParameter; - astFormalParameter.definedVariable = formalParameterType; - setFormalParameterType(formalParameterType, astFormalParameter, null, builtInTypeScope, preLinkingPhase); - - return true; - } - - def package TFormalParameter createFormalParameter(FormalParameter n4FormalParameter, BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - return createFormalParameter(n4FormalParameter, null, builtInTypeScope, preLinkingPhase); - } - - /** - * Creates a TFormalParameter for the given FormalParameter from the AST. - * - * @param defaultTypeRef will be used in case there is no declared type for the formal parameter; - * this may be null and in this case any will be - * the formal parameter's actual type. - */ - def package TFormalParameter createFormalParameter(FormalParameter astFormalParameter, TypeRef defaultTypeRef, BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - // note: we also build an fpar if astFormalParameter.name===null (otherwise the AST and types model - // would have different number of formal parameters in case of a broken AST, messing up indices, etc.) - val formalParameterType = TypesFactory::eINSTANCE.createTFormalParameter(); - formalParameterType.name = astFormalParameter.name; - formalParameterType.variadic = astFormalParameter.variadic; - formalParameterType.astInitializer = null; - formalParameterType.hasInitializerAssignment = astFormalParameter.hasInitializerAssignment; - setFormalParameterType(formalParameterType, astFormalParameter, defaultTypeRef, builtInTypeScope, preLinkingPhase) - - copyAnnotations(formalParameterType, astFormalParameter, preLinkingPhase) - - formalParameterType.astElement = astFormalParameter; - astFormalParameter.definedVariable = formalParameterType; - - return formalParameterType; - } - - /** - * @param formalParameterType the type system related parameter type to be set - * @param astFormalParameter the AST related parameter which is to be copied to the former - */ - def private void setFormalParameterType(TFormalParameter formalParameterType, FormalParameter astFormalParameter, - TypeRef defaultTypeRef, BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase - ) { - if (!preLinkingPhase) - formalParameterType.typeRef = TypeUtils.copyWithProxies(astFormalParameter.declaredTypeRefInAST) ?: getDefaultParameterType(defaultTypeRef, astFormalParameter, builtInTypeScope) - } - - def private TypeRef getDefaultParameterType(TypeRef defaultTypeRef, - FormalParameter astFormalParameter, BuiltInTypeScope builtInTypeScope - ) { - if (astFormalParameter.initializer !== null) { - TypeUtils.createDeferredTypeRef - } else if (defaultTypeRef === null) { - builtInTypeScope.anyTypeRef - } else { - TypeUtils.copy(defaultTypeRef) - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFunctionDefinitionTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFunctionDefinitionTypesBuilder.java new file mode 100644 index 0000000000..f80f6dc767 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFunctionDefinitionTypesBuilder.java @@ -0,0 +1,213 @@ +/** + * 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.typesbuilder; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filterNull; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.n4JS.FunctionDeclaration; +import org.eclipse.n4js.n4JS.FunctionDefinition; +import org.eclipse.n4js.n4JS.FunctionExpression; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.types.utils.TypeUtils; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * Type builder for function declaration or expression builder. + */ +// TODO we temporarily create a BuiltInTypeScope in order to get primitive types. This may be changed by passing in this +// scope, one this method is called by the type system +@Singleton +public class N4JSFunctionDefinitionTypesBuilder extends AbstractFunctionDefinitionTypesBuilder { + + @Inject + N4JSFormalParameterTypesBuilder _n4JSFormalParameterTypesBuilder; + @Inject + N4JSTypeVariableTypesBuilder _n4JSTypeVariableTypesBuilder; + @Inject + N4JSVariableStatementTypesBuilder _n4JSVariableStatementTypesBuilder; + @Inject + N4JSTypesBuilderHelper _n4JSTypesBuilderHelper; + + boolean relinkTFunction(FunctionDeclaration functionDecl, AbstractNamespace target, boolean preLinkingPhase, + int idx) { + EObject functionDefinedType = (EObject) functionDecl + .eGet(N4JSPackage.eINSTANCE.getTypeDefiningElement_DefinedType(), false); + if (functionDefinedType != null && !functionDefinedType.eIsProxy()) { + throw new IllegalStateException("TFunction already created for FunctionDeclaration"); + } + + if (functionDecl.getName() == null) { + return false; + } + + TFunction functionType = target.getFunctions().get(idx); + _n4JSTypesBuilderHelper.ensureEqualName(functionDecl, functionType); + + relinkFormalParameters(functionType, functionDecl, preLinkingPhase); + functionType.setAstElement(functionDecl); + functionDecl.setDefinedType(functionType); + + return true; + } + + /** + * Creates TFunction for the given function declaration and adds it to the modules top level types (as function + * declarations are only allowed on top level). + * + * @param functionDecl + * declaration for which the TFunction is created, must not be linked to a TFunction yet (i.e. its + * defined type must be null). + * @param target + * the module to which the newly created TFunction is added + */ + void createTFunction(FunctionDeclaration functionDecl, AbstractNamespace target, boolean preLinkingPhase) { + EObject functionDefinedType = (EObject) functionDecl + .eGet(N4JSPackage.eINSTANCE.getTypeDefiningElement_DefinedType(), false); + if (functionDefinedType != null && !functionDefinedType.eIsProxy()) { + throw new IllegalStateException("TFunction already created for FunctionDeclaration"); + } + + if (functionDecl.getName() == null) { + return; + } + + BuiltInTypeScope builtInTypeScope = BuiltInTypeScope.get(functionDecl.eResource().getResourceSet()); + TFunction functionType = createAndLinkTFunction(functionDecl); + _n4JSVariableStatementTypesBuilder.createImplicitArgumentsVariable(functionDecl, target, builtInTypeScope, + preLinkingPhase); + + addFormalParameters(functionType, functionDecl, builtInTypeScope, preLinkingPhase); + _n4JSTypesBuilderHelper.setTypeAccessModifier(functionType, functionDecl); + _n4JSTypesBuilderHelper.setProvidedByRuntime(functionType, functionDecl, preLinkingPhase); + setReturnType(functionType, functionDecl, builtInTypeScope, preLinkingPhase); + _n4JSTypeVariableTypesBuilder.addTypeParameters(functionType, functionDecl, preLinkingPhase); + _n4JSTypesBuilderHelper.setDeclaredThisTypeFromAnnotation(functionType, functionDecl, preLinkingPhase); + _n4JSTypesBuilderHelper.copyAnnotations(functionType, functionDecl, preLinkingPhase); + functionType.setDeclaredAsync(functionDecl.isAsync());// TODO change to declaredAsync once the annotation is + // gone + functionType.setDeclaredGenerator(functionDecl.isGenerator()); + + // set container + target.getFunctions().add(functionType); + } + + /** + * Creates TFunction for the given function expression and adds it to the module. Note that this method applies only + * to expressions that define a function, not to function type expressions that merely define a function type (the + * latter are represented in the AST and TModule by a node of type FunctionTypeExpression from + * Types.xcore). + *

+ * Creating a TFunction for a function expression becomes a bit tricky when type inference has to be used to infer + * the types of one or more formal parameters and/or the return value. These are the steps involved: + *

    + *
  1. method {@link #createTFunction(FunctionExpression,AbstractNamespace,boolean)} creates an initial TFunction in + * which the type of every fpar may(!) be a ComputedTypeRef and the return type may(!) be a ComputedTypeRef. + * ComputedTypeRefs are only used if there is no declared type available. + *
  2. when the first(!) of these ComputedTypeRefs is resolved (and only for the first!), then method + * resolveTFunction(ComputedTypeRef,TFunction,FunctionExpression,BuiltInTypeScope) is invoked. This method will + * handle the resolution of all ComputedTypeRefs of the given TFunction in one step (to avoid unnecessary repeated + * inference of the expected type; note: caching does not help here, because we call judgment 'expectedTypeIn' and + * not 'type'). + *
+ */ + void createTFunction(FunctionExpression functionExpr, AbstractNamespace target, boolean preLinkingPhase) { + EObject functionDefinedType = (EObject) functionExpr + .eGet(N4JSPackage.eINSTANCE.getTypeDefiningElement_DefinedType(), false); + if (functionDefinedType != null && !functionDefinedType.eIsProxy()) { + throw new IllegalStateException("TFunction already created for FunctionExpression"); + } + + BuiltInTypeScope builtInTypeScope = BuiltInTypeScope.get(functionExpr.eResource().getResourceSet()); + TFunction functionType = createAndLinkTFunction(functionExpr); + _n4JSVariableStatementTypesBuilder.createImplicitArgumentsVariable(functionExpr, target, builtInTypeScope, + preLinkingPhase); + + addFormalParametersWithInferredType(functionType, functionExpr, builtInTypeScope, preLinkingPhase); + setReturnTypeWithInferredType(functionType, functionExpr, preLinkingPhase); + _n4JSTypeVariableTypesBuilder.addTypeParameters(functionType, functionExpr, preLinkingPhase); + _n4JSTypesBuilderHelper.setDeclaredThisTypeFromAnnotation(functionType, functionExpr, preLinkingPhase); + + _n4JSTypesBuilderHelper.copyAnnotations(functionType, functionExpr, preLinkingPhase); + + // set container + target.getContainingModule().getInternalTypes().add(functionType); + } + + /** + * Same as + * {@link AbstractFunctionDefinitionTypesBuilder#addFormalParameters(TFunction,FunctionDefinition,BuiltInTypeScope,boolean)}, + * but uses a ComputedTypeRef as the fpar's type if the type has to be inferred. + */ + private void addFormalParametersWithInferredType(TFunction functionType, FunctionExpression functionExpr, + BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { + + functionType.getFpars().addAll( + toList(filterNull(map(functionExpr.getFpars(), + fpar -> _n4JSFormalParameterTypesBuilder.createFormalParameter(fpar, + TypeUtils.createDeferredTypeRef(), + // TypeUtils.createComputedTypeRef([resolveAllComputedTypeRefsInTFunction(functionType,functionExpr,builtInTypeScope)]), + builtInTypeScope, + preLinkingPhase))))); + } + + /** + * Same as + * {@link AbstractFunctionDefinitionTypesBuilder#setReturnType(TFunction,FunctionDefinition,BuiltInTypeScope,boolean)}, + * but uses a ComputedTypeRef as the return type if the type has to be inferred. + */ + private void setReturnTypeWithInferredType(TFunction functionType, FunctionExpression functionExpr, + boolean preLinkingPhase) { + if (!preLinkingPhase) { + /* + * TODO IDE-1579 this branch skips makePromiseIfAsync. Question: could this result in 'void' as inferred + * return type (for an async method)? + */ + TypeRef copy = TypeUtils.copyWithProxies(functionExpr.getDeclaredReturnTypeRefInAST()); + functionType.setReturnTypeRef(copy != null ? copy : TypeUtils.createDeferredTypeRef()); + // note: handling of the return type of async functions not done here, see + // TypeProcessor#handleAsyncFunctionDeclaration() + } + } + + private TFunction createAndLinkTFunction(FunctionDefinition functionDef) { + TFunction functionType = this.createTFunction(); + if (functionDef instanceof FunctionDeclaration) { + functionType.setExternal(((FunctionDeclaration) functionDef).isExternal()); + } + functionType.setName(functionDef.getName()); // maybe null in case of function expression + functionType.setDeclaredAsync(functionDef.isAsync()); // TODO change to declaredAsync when annotation is removed + functionType.setDeclaredGenerator(functionDef.isGenerator());// TODO change to declaredAsync when annotation is + // removed + + // link + functionType.setAstElement(functionDef); + functionDef.setDefinedType(functionType); + + return functionType; + } + + /** + * Creates a new plain instance of {@link TFunction}. + */ + private TFunction createTFunction() { + return TypesFactory.eINSTANCE.createTFunction(); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFunctionDefinitionTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFunctionDefinitionTypesBuilder.xtend deleted file mode 100644 index a6462a9606..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSFunctionDefinitionTypesBuilder.xtend +++ /dev/null @@ -1,184 +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.typesbuilder - -import com.google.inject.Inject -import com.google.inject.Singleton -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.n4JS.FunctionDeclaration -import org.eclipse.n4js.n4JS.FunctionDefinition -import org.eclipse.n4js.n4JS.FunctionExpression -import org.eclipse.n4js.n4JS.N4JSPackage -import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.TFunction -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.types.utils.TypeUtils - -/** - * Type builder for function declaration or expression builder. - */ -// TODO we temporarily create a BuiltInTypeScope in order to get primitive types. This may be changed by passing in this scope, one this method is called by the typesystem -@Singleton -public class N4JSFunctionDefinitionTypesBuilder extends AbstractFunctionDefinitionTypesBuilder { - - @Inject extension N4JSFormalParameterTypesBuilder - @Inject extension N4JSTypeVariableTypesBuilder - @Inject extension N4JSVariableStatementTypesBuilder - @Inject extension N4JSTypesBuilderHelper - - def package boolean relinkTFunction(FunctionDeclaration functionDecl, AbstractNamespace target, boolean preLinkingPhase, int idx) { - val functionDefinedType = functionDecl.eGet(N4JSPackage.eINSTANCE.typeDefiningElement_DefinedType, false) as EObject; - if (functionDefinedType !== null && ! functionDefinedType.eIsProxy) { - throw new IllegalStateException("TFunction already created for FunctionDeclaration"); - } - - if (functionDecl.name === null) { - return false; - } - - val TFunction functionType = target.functions.get(idx) - ensureEqualName(functionDecl, functionType); - - functionType.relinkFormalParameters(functionDecl, preLinkingPhase) - functionType.astElement = functionDecl - functionDecl.definedType = functionType - - return true; - } - - /** - * Creates TFunction for the given function declaration and adds it to the modules top level types - * (as function declarations are only allowed on top level). - * - * @param functionDecl declaration for which the TFunction is created, must not be linked to a TFunction yet (i.e. its defined type must be null). - * @param target the module to which the newly created TFunction is added - */ - def package void createTFunction(FunctionDeclaration functionDecl, AbstractNamespace target, boolean preLinkingPhase) { - val functionDefinedType = functionDecl.eGet(N4JSPackage.eINSTANCE.typeDefiningElement_DefinedType, false) as EObject; - if (functionDefinedType !== null && ! functionDefinedType.eIsProxy) { - throw new IllegalStateException("TFunction already created for FunctionDeclaration"); - } - - if (functionDecl.name === null) { - return; - } - - val builtInTypeScope = BuiltInTypeScope.get(functionDecl.eResource.resourceSet) - val functionType = functionDecl.createAndLinkTFunction(preLinkingPhase) - functionDecl.createImplicitArgumentsVariable(target, builtInTypeScope, preLinkingPhase); - - functionType.addFormalParameters(functionDecl, builtInTypeScope, preLinkingPhase) - functionType.setTypeAccessModifier(functionDecl) - functionType.setProvidedByRuntime(functionDecl, preLinkingPhase) - functionType.setReturnType(functionDecl, builtInTypeScope, preLinkingPhase) - functionType.addTypeParameters(functionDecl, preLinkingPhase) - functionType.setDeclaredThisTypeFromAnnotation(functionDecl, preLinkingPhase) - functionType.copyAnnotations(functionDecl, preLinkingPhase) - functionType.declaredAsync = functionDecl.async // TODO change to declaredAsync once the annotation is gone - functionType.declaredGenerator = functionDecl.generator - - // set container - target.functions += functionType - } - - /** - * Creates TFunction for the given function expression and adds it to the module. Note that this method applies - * only to expressions that define a function, not to function type expressions that merely define a function - * type (the latter are represented in the AST and TModule by a node of type FunctionTypeExpression - * from Types.xcore). - *

- * Creating a TFunction for a function expression becomes a bit tricky when type inference has to be used to - * infer the types of one or more formal parameters and/or the return value. These are the steps involved: - *

    - *
  1. method {@link #createTFunction(FunctionExpression,TModule,boolean)} creates an initial TFunction in - * which the type of every fpar may(!) be a ComputedTypeRef and the return type may(!) be a ComputedTypeRef. - * ComputedTypeRefs are only used if there is no declared type available. - *
  2. when the first(!) of these ComputedTypeRefs is resolved (and only for the first!), then method - * {@link #resolveTFunction(ComputedTypeRef,TFunction,FunctionExpression,BuiltInTypeScope)} - * is invoked. This method will handle the resolution of all ComputedTypeRefs of the given TFunction in - * one step (to avoid unnecessary repeated inference of the expected type; note: caching does not help - * here, because we call judgment 'expectedTypeIn' and not 'type'). - *
- */ - def package void createTFunction(FunctionExpression functionExpr, AbstractNamespace target, boolean preLinkingPhase) { - val functionDefinedType = functionExpr.eGet(N4JSPackage.eINSTANCE.typeDefiningElement_DefinedType, false) as EObject; - if (functionDefinedType !== null && ! functionDefinedType.eIsProxy) { - throw new IllegalStateException("TFunction already created for FunctionExpression"); - } - - val builtInTypeScope = BuiltInTypeScope.get(functionExpr.eResource.resourceSet) - val functionType = functionExpr.createAndLinkTFunction(preLinkingPhase) - functionExpr.createImplicitArgumentsVariable(target, builtInTypeScope, preLinkingPhase); - - functionType.addFormalParametersWithInferredType(functionExpr, builtInTypeScope, preLinkingPhase) - functionType.setReturnTypeWithInferredType(functionExpr, builtInTypeScope, preLinkingPhase) - functionType.addTypeParameters(functionExpr, preLinkingPhase) - functionType.setDeclaredThisTypeFromAnnotation(functionExpr, preLinkingPhase) - - functionType.copyAnnotations(functionExpr, preLinkingPhase) - - // set container - target.containingModule.internalTypes += functionType - } - - /** - * Same as {@link AbstractFunctionDefinitionTypesBuilder#addFormalParameters(TFunction,FunctionDefinition,BuiltInTypeScope,boolean)}, - * but uses a ComputedTypeRef as the fpar's type if the type has to be inferred. - */ - def private void addFormalParametersWithInferredType(TFunction functionType, FunctionExpression functionExpr, - BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - functionType.fpars.addAll( - functionExpr.fpars.map[it.createFormalParameter( - TypeUtils.createDeferredTypeRef, //TypeUtils.createComputedTypeRef([resolveAllComputedTypeRefsInTFunction(functionType,functionExpr,builtInTypeScope)]), - builtInTypeScope, - preLinkingPhase - )].filterNull); - } - - /** - * Same as {@link AbstractFunctionDefinitionTypesBuilder#setReturnType(TFunction,FunctionDefinition,BuiltInTypeScope,boolean)}, - * but uses a ComputedTypeRef as the return type if the type has to be inferred. - */ - def private void setReturnTypeWithInferredType(TFunction functionType, FunctionExpression functionExpr, - BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - if (!preLinkingPhase) - /* - * TODO IDE-1579 this branch skips makePromiseIfAsync. - * Question: could this result in 'void' as inferred return type (for an async method)? - */ - functionType.returnTypeRef = TypeUtils.copyWithProxies(functionExpr.declaredReturnTypeRefInAST) ?: TypeUtils.createDeferredTypeRef - // note: handling of the return type of async functions not done here, see TypeProcessor#handleAsyncFunctionDeclaration() - } - - def private TFunction createAndLinkTFunction(FunctionDefinition functionDef, boolean preLinkingPhase) { - val functionType = this.createTFunction(functionDef); - if(functionDef instanceof FunctionDeclaration) { - functionType.external = functionDef.external; - } - functionType.name = functionDef.name; // maybe null in case of function expression - functionType.declaredAsync = functionDef.isAsync // TODO change to declaredAsync when annotation is removed - functionType.declaredGenerator = functionDef.generator // TODO change to declaredAsync when annotation is removed - - // link - functionType.astElement = functionDef - functionDef.definedType = functionType - - return functionType - } - - /** - * Creates a new plain instance of {@link TFunction}. - */ - def private TFunction createTFunction(FunctionDefinition functionDef) { - return TypesFactory::eINSTANCE.createTFunction(); - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSGetterTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSGetterTypesBuilder.java new file mode 100644 index 0000000000..6946c2f43b --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSGetterTypesBuilder.java @@ -0,0 +1,106 @@ +/** + * 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.typesbuilder; + +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.n4JS.N4GetterDeclaration; +import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope; +import org.eclipse.n4js.ts.typeRefs.ThisTypeRef; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TGetter; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.types.utils.TypeUtils; + +import com.google.inject.Inject; + +/** + */ +class N4JSGetterTypesBuilder extends AbstractFunctionDefinitionTypesBuilder { + + @Inject + N4JSVariableStatementTypesBuilder _n4JSVariableStatementTypesBuilder; + @Inject + N4JSTypesBuilderHelper _n4JSTypesBuilderHelper; + + boolean canCreate(N4GetterDeclaration n4Getter) { + return n4Getter.getName() != null || n4Getter.hasComputedPropertyName(); + } + + boolean relinkGetter(N4GetterDeclaration n4Getter, TGetter tGetter, + @SuppressWarnings("unused") boolean preLinkingPhase) { + + if (!canCreate(n4Getter)) { + return false; + } + + _n4JSTypesBuilderHelper.ensureEqualName(n4Getter, tGetter); + tGetter.setAstElement(n4Getter); + n4Getter.setDefinedGetter(tGetter); + return true; + } + + TGetter createGetter(N4GetterDeclaration n4Getter, @SuppressWarnings("unused") TClassifier classifierType, + AbstractNamespace target, boolean preLinkingPhase) { + + if (!canCreate(n4Getter)) { + return null; + } + TGetter getterType = TypesFactory.eINSTANCE.createTGetter(); + _n4JSTypesBuilderHelper.setMemberName(getterType, n4Getter); + + getterType.setDeclaredAbstract(n4Getter.isAbstract()); + getterType.setDeclaredStatic(n4Getter.isDeclaredStatic()); + getterType.setDeclaredFinal(n4Getter.isDeclaredFinal()); + getterType.setOptional(n4Getter.isOptional()); + getterType.setDeclaredOverride(AnnotationDefinition.OVERRIDE.hasAnnotation(n4Getter)); + + getterType.setHasNoBody(n4Getter.getBody() == null + && !AnnotationDefinition.PROVIDES_DEFAULT_IMPLEMENTATION.hasAnnotation(n4Getter)); + + // TODO if possible, remove, see AbstractFunctionDefinitionTypesBuilder + BuiltInTypeScope builtInTypeScope = BuiltInTypeScope.get(n4Getter.eResource().getResourceSet()); + _n4JSVariableStatementTypesBuilder.createImplicitArgumentsVariable(n4Getter, target, builtInTypeScope, + preLinkingPhase); + + setMemberAccessModifier(getterType, n4Getter); + setReturnTypeConsideringThis(getterType, n4Getter, builtInTypeScope, preLinkingPhase); + _n4JSTypesBuilderHelper.setDeclaredThisTypeFromAnnotation(getterType, n4Getter, preLinkingPhase); + + _n4JSTypesBuilderHelper.copyAnnotations(getterType, n4Getter, preLinkingPhase); + + getterType.setAstElement(n4Getter); + n4Getter.setDefinedGetter(getterType); + return getterType; + } + + private void setMemberAccessModifier(TGetter getterType, N4GetterDeclaration n4Getter) { + _n4JSTypesBuilderHelper.setMemberAccessModifier( + modifier -> getterType.setDeclaredMemberAccessModifier(modifier), + n4Getter.getDeclaredModifiers(), n4Getter.getAnnotations()); + } + + /** + * Sets the return type. If the declared return type is 'this', a ComputedTypeRef will be created to generate a + * bound this type. + */ + private void setReturnTypeConsideringThis(TGetter getterType, N4GetterDeclaration getterDecl, + BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { + // TODO: explicitly differentiate between declared and inferred type + if (getterDecl.getDeclaredTypeRefInAST() instanceof ThisTypeRef) { + // special case: TypingASTWalker will create a BoundThisTypeRef via Xsemantics judgment 'thisTypeRef' + getterType.setTypeRef(TypeUtils.createDeferredTypeRef()); + } else { + // standard case + setReturnType(getterType, getterDecl, builtInTypeScope, preLinkingPhase); + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSGetterTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSGetterTypesBuilder.xtend deleted file mode 100644 index 75eb1e5aac..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSGetterTypesBuilder.xtend +++ /dev/null @@ -1,99 +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.typesbuilder - -import com.google.inject.Inject -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.n4JS.N4GetterDeclaration -import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope -import org.eclipse.n4js.ts.typeRefs.ThisTypeRef -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.MemberAccessModifier -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TGetter -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.types.utils.TypeUtils - -/** - */ -package class N4JSGetterTypesBuilder extends AbstractFunctionDefinitionTypesBuilder { - - @Inject extension N4JSVariableStatementTypesBuilder - @Inject extension N4JSTypesBuilderHelper - - def boolean canCreate(N4GetterDeclaration n4Getter) { - return n4Getter.name !== null || n4Getter.hasComputedPropertyName; - } - - def package boolean relinkGetter(N4GetterDeclaration n4Getter, TGetter tGetter, boolean preLinkingPhase) { - if (!canCreate(n4Getter)) { - return false - } - - ensureEqualName(n4Getter, tGetter); - tGetter.astElement = n4Getter - n4Getter.definedGetter = tGetter - return true - } - - def package TGetter createGetter(N4GetterDeclaration n4Getter, TClassifier classifierType, AbstractNamespace target, boolean preLinkingPhase) { - if (!canCreate(n4Getter)) { - return null - } - val getterType = TypesFactory::eINSTANCE.createTGetter - getterType.setMemberName(n4Getter); - - getterType.declaredAbstract = n4Getter.abstract - getterType.declaredStatic = n4Getter.declaredStatic - getterType.declaredFinal = n4Getter.declaredFinal - getterType.optional = n4Getter.optional; - getterType.declaredOverride = AnnotationDefinition.OVERRIDE.hasAnnotation(n4Getter); - - getterType.hasNoBody = n4Getter.body ===null&& !AnnotationDefinition.PROVIDES_DEFAULT_IMPLEMENTATION.hasAnnotation(n4Getter); - - // TODO if possible, remove, see AbstractFunctionDefinitionTypesBuilder - val builtInTypeScope = BuiltInTypeScope.get(n4Getter.eResource.resourceSet) - n4Getter.createImplicitArgumentsVariable(target, builtInTypeScope, preLinkingPhase); - - getterType.setMemberAccessModifier(n4Getter) - getterType.setReturnTypeConsideringThis(n4Getter, builtInTypeScope, preLinkingPhase) - getterType.setDeclaredThisTypeFromAnnotation(n4Getter, preLinkingPhase) - - getterType.copyAnnotations(n4Getter, preLinkingPhase) - - getterType.astElement = n4Getter - n4Getter.definedGetter = getterType - getterType; - } - - def private void setMemberAccessModifier(TGetter getterType, N4GetterDeclaration n4Getter) { - setMemberAccessModifier([MemberAccessModifier modifier | - getterType.declaredMemberAccessModifier = modifier - ], n4Getter.declaredModifiers, n4Getter.annotations) - } - - /** - * Sets the return type. If the declared return type is 'this', a ComputedTypeRef will - * be created to generate a bound this type. - */ - def private void setReturnTypeConsideringThis(TGetter getterType, N4GetterDeclaration getterDecl, - BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - // TODO: explicitly differentiate between declared and inferred type - if(getterDecl.declaredTypeRefInAST instanceof ThisTypeRef) { - // special case: TypingASTWalker will create a BoundThisTypeRef via Xsemantics judgment 'thisTypeRef' - getterType.typeRef = TypeUtils.createDeferredTypeRef - } - else { - // standard case - getterType.setReturnType(getterDecl, builtInTypeScope, preLinkingPhase) - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSImportTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSImportTypesBuilder.java new file mode 100644 index 0000000000..26a4a6babe --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSImportTypesBuilder.java @@ -0,0 +1,203 @@ +/** + * Copyright (c) 2018 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.typesbuilder; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType; +import org.eclipse.n4js.ts.types.TDynamicElement; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.ts.types.TypesFactory; + +import com.google.inject.Inject; + +/** + * A types builder that creates/relinks type model elements related to imports, as described + * {@link #createTypeModelElementsForImports(Script, TModule, boolean) here}. + */ +class N4JSImportTypesBuilder { + + @Inject + N4JSTypesBuilderHelper _N4JSTypesBuilderHelper; + + /** + * Relinks the elements created by {@link #createTypeModelElementsForImports(Script, TModule, boolean)}. + *

+ * If for a given {@link NamespaceImportSpecifier} no {@link ModuleNamespaceVirtualType} instance exists yet, a new + * one is created and added to the module's {@link TModule#getInternalTypes()} + */ + void relinkTypeModelElementsForImports(Script script, TModule target, boolean preLinkingPhase) { + if (preLinkingPhase) { + return; + } + Map namespaceTypesByName = getExistingNamespaceTypesByName(target); + Map dynElemsByName = getExistingDynamicElementsByName(target); + List ids = toList(filter(script.getScriptElements(), ImportDeclaration.class)); + + for (ImportDeclaration importDecl : ids) { + NamespaceImportSpecifier namespaceImport = getNamespaceImportSpecifier(importDecl); + if (namespaceImport != null) { + TModule importedModule = (TModule) importDecl.eGet(N4JSPackage.eINSTANCE.getModuleRef_Module(), false); + ModuleNamespaceVirtualType existingNamespaceType = namespaceTypesByName.get(namespaceImport.getAlias()); + + if (existingNamespaceType != null) { + // if a namespace import type exists, relink it to the new AST + relinkNamespaceType(existingNamespaceType, namespaceImport, importedModule); + } else { + // otherwise re-create a new namespace import type and add it to the internal types of the module + _N4JSTypesBuilderHelper.addNewModuleNamespaceVirtualType(target, namespaceImport.getAlias(), + importedModule, namespaceImport.isDeclaredDynamic(), namespaceImport); + } + + } else { + + for (ImportSpecifier importSpec : importDecl.getImportSpecifiers()) { + if (importSpec instanceof NamedImportSpecifier) { // includes DefaultImportSpecifier + NamedImportSpecifier nis = (NamedImportSpecifier) importSpec; + if (importSpec.isDeclaredDynamic()) { + TDynamicElement existingDynElem = dynElemsByName.get(getNameForDynamicElement(nis)); + + if (existingDynElem != null) { + relinkDynamicElement(existingDynElem, nis); + } else { + target.getInternalDynamicElements().add(createDynamicElement(nis)); + } + } + } + } + } + } + } + + /** + * Creates ... + *

    + *
  • {@link ModuleNamespaceVirtualType} instances for namespace imports. + *
  • {@link TDynamicElement} instances for dynamic named and default imports. + *
+ */ + void createTypeModelElementsForImports(Script script, TModule target, boolean preLinkingPhase) { + if (preLinkingPhase) { + return; + } + List ids = toList(filter(script.getScriptElements(), ImportDeclaration.class)); + for (ImportDeclaration importDecl : ids) { + NamespaceImportSpecifier namespaceImport = getNamespaceImportSpecifier(importDecl); + if (namespaceImport != null) { + TModule importedModule = (TModule) importDecl.eGet(N4JSPackage.eINSTANCE.getModuleRef_Module(), + false); + _N4JSTypesBuilderHelper.addNewModuleNamespaceVirtualType(target, namespaceImport.getAlias(), + importedModule, namespaceImport.isDeclaredDynamic(), namespaceImport); + } else { + for (ImportSpecifier importSpec : importDecl.getImportSpecifiers()) { + if (importSpec instanceof NamedImportSpecifier) { // includes DefaultImportSpecifier + if (importSpec.isDeclaredDynamic()) { + target.getInternalDynamicElements() + .add(createDynamicElement((NamedImportSpecifier) importSpec)); + } + } + } + } + } + } + + private TDynamicElement createDynamicElement(NamedImportSpecifier importSpecifier) { + TDynamicElement elem = TypesFactory.eINSTANCE.createTDynamicElement(); + elem.setName(getNameForDynamicElement(importSpecifier)); + elem.setAstElement(importSpecifier); + importSpecifier.setDefinedDynamicElement(elem); + return elem; + } + + private String getNameForDynamicElement(NamedImportSpecifier importSpecifier) { + return importSpecifier.getAlias() != null ? importSpecifier.getAlias() + : importSpecifier.getImportedElementAsText(); + } + + /** + * Obtains the {@link NamespaceImportSpecifier} of the given {@code importDeclaration}. + * + * Returns {@code null}, if the given import declaration does not represent a namespace import. + */ + private NamespaceImportSpecifier getNamespaceImportSpecifier(ImportDeclaration importDeclaration) { + List namespaceImportSpecifiers = toList( + filter(importDeclaration.getImportSpecifiers(), NamespaceImportSpecifier.class)); + if (!namespaceImportSpecifiers.isEmpty()) { + return namespaceImportSpecifiers.get(0); + } else { + return null; + } + } + + /** + * Relinks the given {@link ModuleNamespaceVirtualType} to the given import specifier. + */ + private void relinkNamespaceType(ModuleNamespaceVirtualType namespaceType, + NamespaceImportSpecifier namespaceImportSpecifier, TModule importedModule) { + + namespaceType.setModule(importedModule); + namespaceType.setAstElement(namespaceImportSpecifier); + namespaceImportSpecifier.setDefinedType(namespaceType); + } + + private void relinkDynamicElement(TDynamicElement elem, NamedImportSpecifier namedImportSpecifier) { + elem.setAstElement(namedImportSpecifier); + namedImportSpecifier.setDefinedDynamicElement(elem); + } + + /** + * Returns a map of all existing {@link ModuleNamespaceVirtualType} contained in the given {@link TModule}. + * + * Includes exposed and non-exposed internal types. + */ + private Map getExistingNamespaceTypesByName(TModule module) { + Map namespaceTypesByName = new HashMap<>(); + if (module.getInternalTypes() != null) { + for (Type type : module.getInternalTypes()) { + if (type instanceof ModuleNamespaceVirtualType) { + ModuleNamespaceVirtualType mnvt = (ModuleNamespaceVirtualType) type; + namespaceTypesByName.put(mnvt.getName(), mnvt); + } + } + } + if (module.getExposedInternalTypes() != null) { + for (Type type : module.getExposedInternalTypes()) { + if (type instanceof ModuleNamespaceVirtualType) { + ModuleNamespaceVirtualType mnvt = (ModuleNamespaceVirtualType) type; + namespaceTypesByName.put(mnvt.getName(), mnvt); + } + } + } + return namespaceTypesByName; + } + + private Map getExistingDynamicElementsByName(TModule module) { + HashMap dynElemsByName = new HashMap<>(); + if (module.getInternalDynamicElements() != null) { + for (TDynamicElement elem : module.getInternalDynamicElements()) { + dynElemsByName.put(elem.getName(), elem); + } + } + return dynElemsByName; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSImportTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSImportTypesBuilder.xtend deleted file mode 100644 index 2508d218ab..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSImportTypesBuilder.xtend +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright (c) 2018 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.typesbuilder - -import com.google.inject.Inject -import java.util.HashMap -import java.util.Map -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.N4JSPackage -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType -import org.eclipse.n4js.ts.types.TDynamicElement -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.ts.types.TypesFactory - -/** - * A types builder that creates/relinks type model elements related to imports, as described - * {@link #createTypeModelElementsForImports(Script, TModule, boolean) here}. - */ -class N4JSImportTypesBuilder { - - @Inject extension N4JSTypesBuilderHelper - - /** - * Relinks the elements created by {@link #createTypeModelElementsForImports(Script, TModule, boolean)}. - *

- * If for a given {@link NamespaceImportSpecifier} no {@link ModuleNamespaceVirtualType} - * instance exists yet, a new one is created and added to the module's {@link TModule#internalTypes} - */ - def void relinkTypeModelElementsForImports(Script script, TModule target, boolean preLinkingPhase) { - if (!preLinkingPhase) { - val namespaceTypesByName = getExistingNamespaceTypesByName(target); - val dynElemsByName = getExistingDynamicElementsByName(target); - - for (importDecl : script.scriptElements.filter(ImportDeclaration).toList) { - val namespaceImport = getNamespaceImportSpecifier(importDecl) - if(namespaceImport !== null) { - val importedModule = importDecl.eGet(N4JSPackage.eINSTANCE.moduleRef_Module, false) as TModule - val existingNamespaceType = namespaceTypesByName.get(namespaceImport.alias); - - if (existingNamespaceType !== null) { - // if a namespace import type exists, relink it to the new AST - relinkNamespaceType(existingNamespaceType, namespaceImport, importedModule); - } else { - // otherwise re-create a new namespace import type and add it to the internal types of the module - target.addNewModuleNamespaceVirtualType(namespaceImport.alias, importedModule, namespaceImport.declaredDynamic, namespaceImport); - } - - } else { - - for (importSpec : importDecl.importSpecifiers) { - if (importSpec instanceof NamedImportSpecifier) { // includes DefaultImportSpecifier - if (importSpec.declaredDynamic) { - val existingDynElem = dynElemsByName.get(getNameForDynamicElement(importSpec)); - - if (existingDynElem !== null) { - relinkDynamicElement(existingDynElem, importSpec); - } else { - target.internalDynamicElements += createDynamicElement(importSpec); - } - } - } - } - } - } - } - } - - /** - * Creates ... - *

    - *
  • {@link ModuleNamespaceVirtualType} instances for namespace imports. - *
  • {@link TDynamicElement} instances for dynamic named and default imports. - *
- */ - def void createTypeModelElementsForImports(Script script, TModule target, boolean preLinkingPhase) { - if (!preLinkingPhase) { - for (importDecl : script.scriptElements.filter(ImportDeclaration).toList) { - val namespaceImport = getNamespaceImportSpecifier(importDecl) - if (namespaceImport !== null) { - val importedModule = importDecl.eGet(N4JSPackage.eINSTANCE.moduleRef_Module, - false) as TModule - target.addNewModuleNamespaceVirtualType(namespaceImport.alias, importedModule, namespaceImport.declaredDynamic, namespaceImport); - } else { - for (importSpec : importDecl.importSpecifiers) { - if (importSpec instanceof NamedImportSpecifier) { // includes DefaultImportSpecifier - if (importSpec.declaredDynamic) { - target.internalDynamicElements += createDynamicElement(importSpec); - } - } - } - } - } - } - } - - private def TDynamicElement createDynamicElement(NamedImportSpecifier importSpecifier) { - val elem = TypesFactory.eINSTANCE.createTDynamicElement(); - elem.name = getNameForDynamicElement(importSpecifier); - elem.astElement = importSpecifier; - importSpecifier.definedDynamicElement = elem; - return elem; - } - - private def String getNameForDynamicElement(NamedImportSpecifier importSpecifier) { - return importSpecifier.alias ?: importSpecifier.importedElementAsText; - } - - /** - * Obtains the {@link NamespaceImportSpecifier} of the given {@code importDeclaration}. - * - * Returns {@code null}, if the given import declaration does not represent a namespace import. - */ - private def NamespaceImportSpecifier getNamespaceImportSpecifier(ImportDeclaration importDeclaration) { - val namespaceImportSpecifiers = importDeclaration.importSpecifiers.filter(NamespaceImportSpecifier).toList - if (!namespaceImportSpecifiers.empty) { - return namespaceImportSpecifiers.head - } else { - return null; - } - } - - /** - * Relinks the given {@link ModuleNamespaceVirtualType} to the given import specifier. - */ - private def void relinkNamespaceType(ModuleNamespaceVirtualType namespaceType, - NamespaceImportSpecifier namespaceImportSpecifier, TModule importedModule) { - - namespaceType.module = importedModule; - namespaceType.astElement = namespaceImportSpecifier; - namespaceImportSpecifier.definedType = namespaceType; - } - - private def void relinkDynamicElement(TDynamicElement elem, NamedImportSpecifier namedImportSpecifier) { - elem.astElement = namedImportSpecifier; - namedImportSpecifier.definedDynamicElement = elem; - } - - /** - * Returns a map of all existing {@link ModuleNamespaceVirtualType} contained in the - * given {@link TModule}. - * - * Includes exposed and non-exposed internal types. - */ - private def Map getExistingNamespaceTypesByName(TModule module) { - val namespaceTypesByName = new HashMap(); - if (module.internalTypes !== null) { - module.internalTypes - .filter(ModuleNamespaceVirtualType) - .forEach[type | namespaceTypesByName.put(type.name, type) ] - } - if (module.exposedInternalTypes !== null) { - module.exposedInternalTypes - .filter(ModuleNamespaceVirtualType) - .forEach[type | namespaceTypesByName.put(type.name, type) ] - } - return namespaceTypesByName; - } - - private def Map getExistingDynamicElementsByName(TModule module) { - val dynElemsByName = new HashMap(); - if (module.internalDynamicElements !== null) { - module.internalDynamicElements - .forEach[elem | dynElemsByName.put(elem.name, elem) ] - } - return dynElemsByName; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSInterfaceDeclarationTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSInterfaceDeclarationTypesBuilder.java new file mode 100644 index 0000000000..0c5b5e55ac --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSInterfaceDeclarationTypesBuilder.java @@ -0,0 +1,88 @@ +/** + * 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.typesbuilder; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import org.eclipse.n4js.n4JS.N4InterfaceDeclaration; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.TInterface; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.ts.types.TypingStrategy; +import org.eclipse.n4js.utils.N4JSLanguageUtils; + +/***/ +public class N4JSInterfaceDeclarationTypesBuilder extends N4JSClassifierDeclarationTypesBuilder { + + boolean relinkTInterface(N4InterfaceDeclaration n4Interface, AbstractNamespace target, boolean preLinkingPhase, + int idx) { + if (n4Interface.getName() == null) { // may be null due to syntax errors + return false; + } + + TInterface interfaceType = (TInterface) target.getTypes().get(idx); + relinkClassifierAndMembers(interfaceType, n4Interface, preLinkingPhase); + return true; + } + + /***/ + protected TInterface createTInterface(N4InterfaceDeclaration n4Interface, AbstractNamespace target, + boolean preLinkingPhase) { + if (n4Interface.getName() == null) { + return null; + } + + TInterface interfaceType = createTInterface(n4Interface); + _n4JSTypesBuilderHelper.setTypeAccessModifier(interfaceType, n4Interface); + + interfaceType.setTypingStrategy( + (n4Interface.getTypingStrategy() == TypingStrategy.DEFAULT) ? TypingStrategy.DEFAULT + : // STRUCTURAL_FIELD is not allowed on def site, but maybe we got a wrong input + TypingStrategy.STRUCTURAL); + + _n4JSTypesBuilderHelper.setProvidedByRuntime(interfaceType, n4Interface, preLinkingPhase); + interfaceType.setDeclaredNonStaticPolyfill(N4JSLanguageUtils.isNonStaticPolyfill(n4Interface)); + interfaceType + .setDeclaredCovariantConstructor(_n4JSTypesBuilderHelper.isDeclaredCovariantConstructor(n4Interface)); + _n4JSTypeVariableTypesBuilder.addTypeParameters(interfaceType, n4Interface, preLinkingPhase); + addExtendedInterfaces(interfaceType, n4Interface, preLinkingPhase); + + addFields(interfaceType, n4Interface, preLinkingPhase); + addMethods(interfaceType, n4Interface, target, preLinkingPhase); + + addGetters(interfaceType, n4Interface, target, preLinkingPhase); + addSetters(interfaceType, n4Interface, target, preLinkingPhase); + + _n4JSTypesBuilderHelper.copyAnnotations(interfaceType, n4Interface, preLinkingPhase); + + interfaceType.setAstElement(n4Interface); + n4Interface.setDefinedType(interfaceType); + + target.getTypes().add(interfaceType); + return interfaceType; + } + + private TInterface createTInterface(N4InterfaceDeclaration n4Interface) { + TInterface interfaceType = TypesFactory.eINSTANCE.createTInterface(); + interfaceType.setName(n4Interface.getName()); + interfaceType.setExternal(n4Interface.isExternal()); + + return interfaceType; + } + + private void addExtendedInterfaces(TInterface interfaceType, N4InterfaceDeclaration c, boolean preLinkingPhase) { + if (!preLinkingPhase) { + _n4JSTypesBuilderHelper.addCopyOfReferences(interfaceType.getSuperInterfaceRefs(), + toList(map(c.getSuperInterfaceRefs(), sir -> sir.getTypeRefInAST()))); + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSInterfaceDeclarationTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSInterfaceDeclarationTypesBuilder.xtend deleted file mode 100644 index d44dc65229..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSInterfaceDeclarationTypesBuilder.xtend +++ /dev/null @@ -1,80 +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.typesbuilder - -import org.eclipse.n4js.n4JS.N4InterfaceDeclaration -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.TInterface -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.ts.types.TypingStrategy -import org.eclipse.n4js.utils.N4JSLanguageUtils - -public class N4JSInterfaceDeclarationTypesBuilder extends N4JSClassifierDeclarationTypesBuilder { - - def package boolean relinkTInterface(N4InterfaceDeclaration n4Interface, AbstractNamespace target, boolean preLinkingPhase, int idx) { - if (n4Interface.name === null) { // may be null due to syntax errors - return false; - } - - val TInterface interfaceType = target.types.get(idx) as TInterface - interfaceType.relinkClassifierAndMembers(n4Interface, preLinkingPhase); - return true; - } - - def protected TInterface createTInterface(N4InterfaceDeclaration n4Interface, AbstractNamespace target, boolean preLinkingPhase) { - if (n4Interface.name === null) { - return null; - } - - val interfaceType = createTInterface(n4Interface); - interfaceType.setTypeAccessModifier(n4Interface) - - interfaceType.setTypingStrategy( - if (n4Interface.typingStrategy === TypingStrategy.DEFAULT) { - TypingStrategy.DEFAULT - } else { // STRUCTURAL_FIELD is not allowed on def site, but maybe we got a wrong input - TypingStrategy.STRUCTURAL - }) - - interfaceType.setProvidedByRuntime(n4Interface, preLinkingPhase) - interfaceType.declaredNonStaticPolyfill = N4JSLanguageUtils.isNonStaticPolyfill(n4Interface); - interfaceType.declaredCovariantConstructor = n4Interface.isDeclaredCovariantConstructor; - interfaceType.addTypeParameters(n4Interface, preLinkingPhase) - interfaceType.addExtendedInterfaces(n4Interface, preLinkingPhase) - - interfaceType.addFields(n4Interface, preLinkingPhase) - interfaceType.addMethods(n4Interface, target, preLinkingPhase) - - interfaceType.addGetters(n4Interface, target, preLinkingPhase) - interfaceType.addSetters(n4Interface, target, preLinkingPhase) - - interfaceType.copyAnnotations(n4Interface, preLinkingPhase) - - interfaceType.astElement = n4Interface - n4Interface.definedType = interfaceType - - target.types += interfaceType - return interfaceType; - } - - def private TInterface createTInterface(N4InterfaceDeclaration n4Interface) { - val interfaceType = TypesFactory::eINSTANCE.createTInterface(); - interfaceType.name = n4Interface.name; - interfaceType.external = n4Interface.external; - - return interfaceType - } - - def private void addExtendedInterfaces(TInterface interfaceType, N4InterfaceDeclaration c, boolean preLinkingPhase) { - if (!preLinkingPhase) - addCopyOfReferences(interfaceType.superInterfaceRefs, c.superInterfaceRefs.map[typeRefInAST]) - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSMethodTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSMethodTypesBuilder.java new file mode 100644 index 0000000000..78e40e301c --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSMethodTypesBuilder.java @@ -0,0 +1,173 @@ +/** + * 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.typesbuilder; + +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.exists; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.n4JS.Block; +import org.eclipse.n4js.n4JS.FunctionDeclaration; +import org.eclipse.n4js.n4JS.FunctionDefinition; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.n4JS.N4MethodDeclaration; +import org.eclipse.n4js.n4JS.SuperLiteral; +import org.eclipse.n4js.n4JS.ThisLiteral; +import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope; +import org.eclipse.n4js.ts.typeRefs.ThisTypeRef; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.utils.EcoreUtilN4; +import org.eclipse.n4js.utils.N4JSLanguageUtils; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +@Singleton +class N4JSMethodTypesBuilder extends AbstractFunctionDefinitionTypesBuilder { + + @Inject + N4JSTypeVariableTypesBuilder _n4JSTypeVariableTypesBuilder; + @Inject + N4JSVariableStatementTypesBuilder _n4JSVariableStatementTypesBuilder; + @Inject + N4JSTypesBuilderHelper _n4JSTypesBuilderHelper; + + boolean canCreate(N4MethodDeclaration methodDecl) { + EObject methodDefinedType = (EObject) methodDecl.eGet( + N4JSPackage.eINSTANCE.getTypeDefiningElement_DefinedType(), + false); + if (methodDefinedType != null && !methodDefinedType.eIsProxy()) { + throw new IllegalStateException("TMethod already created for N4MethodDeclaration"); + } + if (methodDecl.getName() == null && !methodDecl.hasComputedPropertyName() && !methodDecl.isCallSignature()) { + return false; + } + return true; + } + + boolean relinkMethod(N4MethodDeclaration methodDecl, TClassifier classifier, boolean preLinkingPhase, int idx) { + if (!canCreate(methodDecl)) { + return false; + } + return relinkMethod(methodDecl, (TMethod) classifier.getOwnedMembers().get(idx), preLinkingPhase); + } + + boolean relinkMethod(N4MethodDeclaration methodDecl, TMethod tMethod, boolean preLinkingPhase) { + TMethod methodType = tMethod; + _n4JSTypesBuilderHelper.ensureEqualName(methodDecl, methodType); + + relinkFormalParameters(methodType, methodDecl, preLinkingPhase); + + // link + methodType.setAstElement(methodDecl); + methodDecl.setDefinedType(methodType); + + return true; + } + + /** + * Creates TMethod for the given method declaration (and links it to that method). + * + * @param methodDecl + * declaration for which the TMethod is created, must not be linked to a TMethod yet (i.e. its defined + * type must be null). + */ + TMethod createMethod(N4MethodDeclaration methodDecl, AbstractNamespace target, boolean preLinkingPhase) { + if (!canCreate(methodDecl)) { + return null; + } + TMethod methodType = TypesFactory.eINSTANCE.createTMethod(); + if (methodDecl.isCallSignature()) { + methodType.setName(N4JSLanguageUtils.CALL_SIGNATURE_NAME); + } else { + _n4JSTypesBuilderHelper.setMemberName(methodType, methodDecl); + } + methodType.setDeclaredAbstract(methodDecl.isAbstract()); + methodType.setDeclaredStatic(methodDecl.isDeclaredStatic()); + methodType.setDeclaredFinal(methodDecl.isDeclaredFinal()); + methodType.setDeclaredOverride(AnnotationDefinition.OVERRIDE.hasAnnotation(methodDecl)); + methodType.setConstructor(methodDecl.isConstructor()); + methodType.setDeclaredAsync(methodDecl.isAsync()); + methodType.setDeclaredGenerator(methodDecl.isGenerator()); + + boolean providesDefaultImpl = AnnotationDefinition.PROVIDES_DEFAULT_IMPLEMENTATION.hasAnnotation(methodDecl); + methodType.setHasNoBody(methodDecl.getBody() == null && !providesDefaultImpl); + + methodType.setLacksThisOrSuperUsage( + hasNonNullBody(methodDecl.getBody()) && !containsThisOrSuperUsage(methodDecl.getBody())); + + BuiltInTypeScope builtInTypeScope = BuiltInTypeScope.get(methodDecl.eResource().getResourceSet()); + _n4JSVariableStatementTypesBuilder.createImplicitArgumentsVariable(methodDecl, target, builtInTypeScope, + preLinkingPhase); + + setMemberAccessModifier(methodType, methodDecl); + _n4JSTypeVariableTypesBuilder.addTypeParameters(methodType, methodDecl, preLinkingPhase); + addFormalParameters(methodType, methodDecl, builtInTypeScope, preLinkingPhase); + setReturnTypeConsideringThis(methodType, methodDecl, builtInTypeScope, preLinkingPhase); + _n4JSTypesBuilderHelper.setDeclaredThisTypeFromAnnotation(methodType, methodDecl, preLinkingPhase); + + _n4JSTypesBuilderHelper.copyAnnotations(methodType, methodDecl, preLinkingPhase); + + // link + methodType.setAstElement(methodDecl); + methodDecl.setDefinedType(methodType); + + return methodType; + } + + private void setMemberAccessModifier(TMethod methodType, N4MethodDeclaration n4Method) { + _n4JSTypesBuilderHelper.setMemberAccessModifier( + modifier -> methodType.setDeclaredMemberAccessModifier(modifier), + n4Method.getDeclaredModifiers(), n4Method.getAnnotations()); + } + + /** + * Sets the return type. If the declared return type is 'this', a ComputedTypeRef will be created to generate a + * bound this type. + */ + private void setReturnTypeConsideringThis(TMethod methodType, N4MethodDeclaration methodDecl, + BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { + if (methodDecl.isConstructor() || methodDecl.getDeclaredReturnTypeRefInAST() instanceof ThisTypeRef) { + // special case: TypeDeferredProcessor will create a BoundThisTypeRef via Xsemantics judgment 'thisTypeRef' + methodType.setReturnTypeRef(TypeUtils.createDeferredTypeRef()); + } else { + // standard case + setReturnType(methodType, methodDecl, builtInTypeScope, preLinkingPhase); + } + } + + private boolean hasNonNullBody(Block body) { + return (null != body) && (null != body.getAllStatements()); + } + + /** + * Checks for the presence of 'this' or 'super' usages in the given body, also including sub-expressions (eg, 'if + * (sub-expr)'), without delving inside function definitions or declarations. + *

+ * Static methods refer to static members via ThisLiteral. + */ + private boolean containsThisOrSuperUsage(Block body) { + return exists(body.getAllStatements(), stmt -> isThisOrSuperUsage(stmt) || + exists(EcoreUtilN4.getAllContentsFiltered(stmt, s -> !isFnDefOrDecl(s)), s -> isThisOrSuperUsage(s))); + } + + private boolean isFnDefOrDecl(EObject ast) { + return ast instanceof FunctionDeclaration || ast instanceof FunctionDefinition; + } + + private boolean isThisOrSuperUsage(EObject expr) { + return expr instanceof SuperLiteral || expr instanceof ThisLiteral; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSMethodTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSMethodTypesBuilder.xtend deleted file mode 100644 index 46eead69b1..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSMethodTypesBuilder.xtend +++ /dev/null @@ -1,166 +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.typesbuilder - -import com.google.inject.Inject -import com.google.inject.Singleton -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.n4JS.Block -import org.eclipse.n4js.n4JS.FunctionDeclaration -import org.eclipse.n4js.n4JS.FunctionDefinition -import org.eclipse.n4js.n4JS.N4JSPackage -import org.eclipse.n4js.n4JS.N4MethodDeclaration -import org.eclipse.n4js.n4JS.SuperLiteral -import org.eclipse.n4js.n4JS.ThisLiteral -import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope -import org.eclipse.n4js.ts.typeRefs.ThisTypeRef -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.MemberAccessModifier -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TMethod -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.utils.EcoreUtilN4 -import org.eclipse.n4js.utils.N4JSLanguageUtils - -@Singleton -package class N4JSMethodTypesBuilder extends AbstractFunctionDefinitionTypesBuilder { - - @Inject extension N4JSTypeVariableTypesBuilder - @Inject extension N4JSVariableStatementTypesBuilder - @Inject extension N4JSTypesBuilderHelper - - def boolean canCreate(N4MethodDeclaration methodDecl) { - val methodDefinedType = methodDecl.eGet(N4JSPackage.eINSTANCE.typeDefiningElement_DefinedType, false) as EObject; - if (methodDefinedType !== null && !methodDefinedType.eIsProxy) { - throw new IllegalStateException("TMethod already created for N4MethodDeclaration"); - } - if (methodDecl.name === null && !methodDecl.hasComputedPropertyName && !methodDecl.callSignature) { - return false; - } - return true; - } - - def package boolean relinkMethod(N4MethodDeclaration methodDecl, TClassifier classifier, boolean preLinkingPhase, int idx) { - if (!canCreate(methodDecl)) { - return false; - } - relinkMethod(methodDecl, classifier.ownedMembers.get(idx) as TMethod, preLinkingPhase); - } - - def package boolean relinkMethod(N4MethodDeclaration methodDecl, TMethod tMethod, boolean preLinkingPhase) { - val methodType = tMethod; - ensureEqualName(methodDecl, methodType); - - methodType.relinkFormalParameters(methodDecl, preLinkingPhase) - - // link - methodType.astElement = methodDecl - methodDecl.definedType = methodType - - return true; - } - - /** - * Creates TMethod for the given method declaration (and links it to that method). - * - * @param methodDecl declaration for which the TMethod is created, must not be linked to a TMethod yet (i.e. its defined type must be null). - * @param preLinkingPhase - */ - def package TMethod createMethod(N4MethodDeclaration methodDecl, AbstractNamespace target, boolean preLinkingPhase) { - if (!canCreate(methodDecl)) { - return null; - } - val methodType = TypesFactory::eINSTANCE.createTMethod(); - if (methodDecl.isCallSignature) { - methodType.name = N4JSLanguageUtils.CALL_SIGNATURE_NAME; - } else { - methodType.setMemberName(methodDecl); - } - methodType.declaredAbstract = methodDecl.abstract - methodType.declaredStatic = methodDecl.declaredStatic - methodType.declaredFinal = methodDecl.declaredFinal - methodType.declaredOverride = AnnotationDefinition.OVERRIDE.hasAnnotation(methodDecl); - methodType.constructor = methodDecl.constructor - methodType.declaredAsync = methodDecl.async - methodType.declaredGenerator = methodDecl.generator - - val providesDefaultImpl = AnnotationDefinition.PROVIDES_DEFAULT_IMPLEMENTATION.hasAnnotation(methodDecl); - methodType.hasNoBody = methodDecl.body===null && !providesDefaultImpl; - - methodType.lacksThisOrSuperUsage = hasNonNullBody(methodDecl.body) && !containsThisOrSuperUsage(methodDecl.body) - - val builtInTypeScope = BuiltInTypeScope.get(methodDecl.eResource.resourceSet) - methodDecl.createImplicitArgumentsVariable(target, builtInTypeScope, preLinkingPhase); - - methodType.setMemberAccessModifier(methodDecl) - methodType.addTypeParameters(methodDecl, preLinkingPhase) - methodType.addFormalParameters(methodDecl, builtInTypeScope, preLinkingPhase) - methodType.setReturnTypeConsideringThis(methodDecl, builtInTypeScope, preLinkingPhase) - methodType.setDeclaredThisTypeFromAnnotation(methodDecl, preLinkingPhase) - - methodType.copyAnnotations(methodDecl, preLinkingPhase) - - // link - methodType.astElement = methodDecl - methodDecl.definedType = methodType - - return methodType; - } - - def private void setMemberAccessModifier(TMethod methodType, N4MethodDeclaration n4Method) { - setMemberAccessModifier([MemberAccessModifier modifier|methodType.declaredMemberAccessModifier = modifier], - n4Method.declaredModifiers, n4Method.annotations) - } - - /** - * Sets the return type. If the declared return type is 'this', a ComputedTypeRef will - * be created to generate a bound this type. - */ - def private void setReturnTypeConsideringThis(TMethod methodType, N4MethodDeclaration methodDecl, - BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - if (methodDecl.isConstructor || methodDecl.declaredReturnTypeRefInAST instanceof ThisTypeRef) { - // special case: TypeDeferredProcessor will create a BoundThisTypeRef via Xsemantics judgment 'thisTypeRef' - methodType.returnTypeRef = TypeUtils.createDeferredTypeRef - } else { - // standard case - methodType.setReturnType(methodDecl, builtInTypeScope, preLinkingPhase) - } - } - - private def boolean hasNonNullBody(Block body) { - (null !== body) && - (null !== body.allStatements) - } - - /** - * Checks for the presence of 'this' or 'super' usages in the given body, - * also including sub-expressions (eg, 'if (sub-expr)'), - * without delving inside function definitions or declarations. - *

- * Static methods refer to static members via ThisLiteral. - */ - private def boolean containsThisOrSuperUsage(Block body) { - body.allStatements.exists[ stmt | - isThisOrSuperUsage(stmt) || - EcoreUtilN4.getAllContentsFiltered(stmt, [!isFnDefOrDecl(it)]).exists[isThisOrSuperUsage(it)]; - ] - } - - private def boolean isFnDefOrDecl(EObject ast) { - ast instanceof FunctionDeclaration || ast instanceof FunctionDefinition - } - - private def boolean isThisOrSuperUsage(EObject expr) { - expr instanceof SuperLiteral || expr instanceof ThisLiteral - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSNamespaceDeclarationTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSNamespaceDeclarationTypesBuilder.java new file mode 100644 index 0000000000..9564275b42 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSNamespaceDeclarationTypesBuilder.java @@ -0,0 +1,63 @@ +/** + * 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.typesbuilder; + +import org.eclipse.n4js.n4JS.N4NamespaceDeclaration; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.TNamespace; +import org.eclipse.n4js.ts.types.TypesFactory; + +@SuppressWarnings("javadoc") +public class N4JSNamespaceDeclarationTypesBuilder extends N4JSClassifierDeclarationTypesBuilder { + + boolean relinkTNamespace(N4NamespaceDeclaration n4Namespace, AbstractNamespace target, + @SuppressWarnings("unused") boolean preLinkingPhase, int idx) { + + if (n4Namespace.getName() == null) { // may be null due to syntax errors + return false; + } + + TNamespace namespaceType = target.getNamespaces().get(idx); + _n4JSTypesBuilderHelper.ensureEqualName(n4Namespace, namespaceType); + + namespaceType.setAstElement(n4Namespace); + n4Namespace.setDefinedType(namespaceType); + + return true; + } + + protected TNamespace createTNamespace(N4NamespaceDeclaration n4Namespace, AbstractNamespace target, + boolean preLinkingPhase) { + if (n4Namespace.getName() == null) { + return null; + } + + TNamespace namespaceType = createTNamespace(n4Namespace); + _n4JSTypesBuilderHelper.setTypeAccessModifier(namespaceType, n4Namespace); + + _n4JSTypesBuilderHelper.setProvidedByRuntime(namespaceType, n4Namespace, preLinkingPhase); + + namespaceType.setAstElement(n4Namespace); + n4Namespace.setDefinedType(namespaceType); + + target.getNamespaces().add(namespaceType); + return namespaceType; + } + + private TNamespace createTNamespace(N4NamespaceDeclaration n4Namespace) { + TNamespace namespaceType = TypesFactory.eINSTANCE.createTNamespace(); + namespaceType.setName(n4Namespace.getName()); + namespaceType.setExternal(n4Namespace.isExternal()); + + return namespaceType; + } + +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSNamespaceDeclarationTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSNamespaceDeclarationTypesBuilder.xtend deleted file mode 100644 index dde09b61a0..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSNamespaceDeclarationTypesBuilder.xtend +++ /dev/null @@ -1,60 +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.typesbuilder - -import org.eclipse.n4js.n4JS.N4NamespaceDeclaration -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.TNamespace -import org.eclipse.n4js.ts.types.TypesFactory - -public class N4JSNamespaceDeclarationTypesBuilder extends N4JSClassifierDeclarationTypesBuilder { - - def package boolean relinkTNamespace(N4NamespaceDeclaration n4Namespace, AbstractNamespace target, boolean preLinkingPhase, int idx) { - if (n4Namespace.name === null) { // may be null due to syntax errors - return false; - } - - val TNamespace namespaceType = target.namespaces.get(idx) - ensureEqualName(n4Namespace, namespaceType); - - namespaceType.astElement = n4Namespace - n4Namespace.definedType = namespaceType - - return true; - } - - def protected TNamespace createTNamespace(N4NamespaceDeclaration n4Namespace, AbstractNamespace target, boolean preLinkingPhase) { - if (n4Namespace.name === null) { - return null; - } - - val namespaceType = createTNamespace(n4Namespace); - namespaceType.setTypeAccessModifier(n4Namespace) - - - namespaceType.setProvidedByRuntime(n4Namespace, preLinkingPhase) - - namespaceType.astElement = n4Namespace - n4Namespace.definedType = namespaceType - - target.namespaces += namespaceType - return namespaceType; - } - - def private TNamespace createTNamespace(N4NamespaceDeclaration n4Namespace) { - val namespaceType = TypesFactory::eINSTANCE.createTNamespace(); - namespaceType.name = n4Namespace.name; - namespaceType.external = n4Namespace.external; - - return namespaceType - } - -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSObjectLiteralTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSObjectLiteralTypesBuilder.java new file mode 100644 index 0000000000..36e7349342 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSObjectLiteralTypesBuilder.java @@ -0,0 +1,206 @@ +/** + * 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.typesbuilder; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.Arrays; + +import org.eclipse.n4js.n4JS.ObjectLiteral; +import org.eclipse.n4js.n4JS.PropertyAssignment; +import org.eclipse.n4js.n4JS.PropertyGetterDeclaration; +import org.eclipse.n4js.n4JS.PropertyMethodDeclaration; +import org.eclipse.n4js.n4JS.PropertyNameValuePair; +import org.eclipse.n4js.n4JS.PropertySetterDeclaration; +import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.TFormalParameter; +import org.eclipse.n4js.ts.types.TStructField; +import org.eclipse.n4js.ts.types.TStructGetter; +import org.eclipse.n4js.ts.types.TStructMember; +import org.eclipse.n4js.ts.types.TStructMethod; +import org.eclipse.n4js.ts.types.TStructSetter; +import org.eclipse.n4js.ts.types.TStructuralType; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.types.utils.TypeUtils; + +import com.google.inject.Inject; + +/** + */ +public class N4JSObjectLiteralTypesBuilder { + + @Inject + N4JSTypesBuilderHelper _n4JSTypesBuilderHelper; + @Inject + N4JSFormalParameterTypesBuilder _n4JSFormalParameterTypesBuilder; + + void createObjectLiteral(ObjectLiteral objectLiteral, AbstractNamespace target, boolean preLinkingPhase) { + BuiltInTypeScope builtInTypeScope = BuiltInTypeScope.get(objectLiteral.eResource().getResourceSet()); + TStructuralType structType = TypesFactory.eINSTANCE.createTStructuralType(); + + for (PropertyAssignment it : objectLiteral.getPropertyAssignments()) { + if (it.getName() != null || it.hasComputedPropertyName()) { + TStructMember typeModelElement = createTypeModelElement(it, builtInTypeScope, preLinkingPhase); + if (typeModelElement != null) { + typeModelElement.setAstElement(it); + structType.getOwnedMembers().add(typeModelElement); + } + } + } + + structType.setAstElement(objectLiteral); + objectLiteral.setDefinedType(structType); + + target.getContainingModule().getInternalTypes().add(structType); + } + + // TODO GH-1337 add support for spread operator + private TStructMember createTypeModelElement( + PropertyAssignment getterDecl, BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { + + if (getterDecl instanceof PropertyGetterDeclaration) { + return createTypeModelElement((PropertyGetterDeclaration) getterDecl, + builtInTypeScope, preLinkingPhase); + } else if (getterDecl instanceof PropertyMethodDeclaration) { + return createTypeModelElement((PropertyMethodDeclaration) getterDecl, + builtInTypeScope, preLinkingPhase); + } else if (getterDecl instanceof PropertyNameValuePair) { + return createTypeModelElement((PropertyNameValuePair) getterDecl, + builtInTypeScope, preLinkingPhase); + } else if (getterDecl instanceof PropertySetterDeclaration) { + return createTypeModelElement((PropertySetterDeclaration) getterDecl, + builtInTypeScope, preLinkingPhase); + } else if (getterDecl != null) { + return null; + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(getterDecl, builtInTypeScope, preLinkingPhase).toString()); + } + } + + /** + * Creates a TStructField. + */ + private TStructField createTypeModelElement( + PropertyNameValuePair nameValuePair, BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { + TStructField field = TypesFactory.eINSTANCE.createTStructField(); + _n4JSTypesBuilderHelper.setMemberName(field, nameValuePair); + field.setOptional(nameValuePair.isDeclaredOptional()); + if (nameValuePair.getDeclaredTypeRefInAST() != null) { + if (!preLinkingPhase) { + field.setTypeRef(TypeUtils.copyWithProxies(nameValuePair.getDeclaredTypeRefInAST())); + } + } else if (nameValuePair.getExpression() != null) { + field.setTypeRef(TypeUtils.createDeferredTypeRef()); + } else { + // FIXME inconsistent with getter/setter case; should use DeferredTypeRef also if expression===null + field.setTypeRef(builtInTypeScope.getAnyTypeRef()); + } + // else { + // // in all other cases: + // // leave it to the corresponding xsemantics rule to infer the type (e.g. from the initializer expression, if + // given) + // if(!preLinkingPhase) { + // field.typeRef = + // TypeUtils.createComputedTypeRef([resolveAllComputedTypeRefsInTStructuralType(structType,objectLiteral,builtInTypeScope)]) + // } + // } + field.setAstElement(nameValuePair); + nameValuePair.setDefinedField(field); + return field; + } + + /** + * Creates a TStructGetter. + */ + private TStructGetter createTypeModelElement(PropertyGetterDeclaration getterDecl, + @SuppressWarnings("unused") BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { + + TStructGetter getter = TypesFactory.eINSTANCE.createTStructGetter(); + _n4JSTypesBuilderHelper.setMemberName(getter, getterDecl); + getter.setOptional(getterDecl.isDeclaredOptional()); + if (getterDecl.getDeclaredTypeRefInAST() != null) { + if (!preLinkingPhase) { + getter.setTypeRef(TypeUtils.copyWithProxies(getterDecl.getDeclaredTypeRefInAST())); + } + } else { + getter.setTypeRef(TypeUtils.createDeferredTypeRef()); + } + getter.setAstElement(getterDecl); + getterDecl.setDefinedGetter(getter); + return getter; + } + + /** + * Creates a TStructSetter. + */ + private TStructSetter createTypeModelElement(PropertySetterDeclaration setterDecl, + @SuppressWarnings("unused") BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { + + TStructSetter setter = TypesFactory.eINSTANCE.createTStructSetter(); + _n4JSTypesBuilderHelper.setMemberName(setter, setterDecl); + setter.setOptional(setterDecl.isDeclaredOptional()); + // IMPORTANT: do not create the formal parameter with N4JSFormalParameterTypesBuilder#createFormalParameter() + // because we here use improved type inference (the type of a getter/setter in an object literal is inferred + // similarly to that of a name/value pair) + TFormalParameter param = TypesFactory.eINSTANCE.createTFormalParameter(); + if (setterDecl.getFpar() != null) { + param.setName(setterDecl.getFpar().getName()); + TypeRef fparDeclTypeRef = setterDecl.getFpar().getDeclaredTypeRefInAST(); + if (fparDeclTypeRef != null) { + if (!preLinkingPhase) { + param.setTypeRef(TypeUtils.copyWithProxies(fparDeclTypeRef)); + } + } else { + param.setTypeRef(TypeUtils.createDeferredTypeRef()); + } + param.setAstElement(setterDecl.getFpar()); + setterDecl.getFpar().setDefinedVariable(param); + } else { + // broken AST + param.setTypeRef(TypeUtils.createDeferredTypeRef()); + // (note: using UnknownTypeRef would make more sense, but PolyComputer expects this, because + // setterDecl.declaredTypeRef===setterDecl?.fpar.declaredTypeRef===null, so setterDecl will be poly) + } + setter.setFpar(param); + setter.setAstElement(setterDecl); + setterDecl.setDefinedSetter(setter); + return setter; + } + + /** + * Creates a TStructMethod. + */ + private TStructMethod createTypeModelElement( + PropertyMethodDeclaration methodDecl, BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { + + TStructMethod result = TypesFactory.eINSTANCE.createTStructMethod(); + _n4JSTypesBuilderHelper.setMemberName(result, methodDecl); + // IMPORTANT: do not create the formal parameters as above for the property setters but instead create them with + // method N4JSFormalParameterTypesBuilder#createFormalParameter() (for consistency with methods in classes) + result.getFpars().addAll( + toList(map(methodDecl.getFpars(), fp -> _n4JSFormalParameterTypesBuilder.createFormalParameter(fp, + builtInTypeScope, preLinkingPhase)))); + if (methodDecl.getDeclaredReturnTypeRefInAST() != null) { + if (!preLinkingPhase) { + result.setReturnTypeRef(TypeUtils.copyWithProxies(methodDecl.getDeclaredReturnTypeRefInAST())); + } + } else { + result.setReturnTypeRef(builtInTypeScope.getVoidTypeRef()); + } + result.setAstElement(methodDecl); + methodDecl.setDefinedType(result); + return result; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSObjectLiteralTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSObjectLiteralTypesBuilder.xtend deleted file mode 100644 index dc61e8a117..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSObjectLiteralTypesBuilder.xtend +++ /dev/null @@ -1,164 +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.typesbuilder; - -import com.google.inject.Inject -import org.eclipse.n4js.n4JS.ObjectLiteral -import org.eclipse.n4js.n4JS.PropertyAssignment -import org.eclipse.n4js.n4JS.PropertyGetterDeclaration -import org.eclipse.n4js.n4JS.PropertyMethodDeclaration -import org.eclipse.n4js.n4JS.PropertyNameValuePair -import org.eclipse.n4js.n4JS.PropertySetterDeclaration -import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.TStructField -import org.eclipse.n4js.ts.types.TStructGetter -import org.eclipse.n4js.ts.types.TStructMember -import org.eclipse.n4js.ts.types.TStructMethod -import org.eclipse.n4js.ts.types.TStructSetter -import org.eclipse.n4js.ts.types.TStructuralType -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.types.utils.TypeUtils - -/** - */ -public class N4JSObjectLiteralTypesBuilder { - - @Inject extension N4JSTypesBuilderHelper - @Inject extension N4JSFormalParameterTypesBuilder - - - def package void createObjectLiteral(ObjectLiteral objectLiteral, AbstractNamespace target, boolean preLinkingPhase) { - val builtInTypeScope = BuiltInTypeScope.get(objectLiteral.eResource.resourceSet) - val TStructuralType structType = TypesFactory.eINSTANCE.createTStructuralType - objectLiteral.propertyAssignments.filter[name!==null || hasComputedPropertyName].forEach [ - val TStructMember typeModelElement = createTypeModelElement(structType, objectLiteral, it, builtInTypeScope, preLinkingPhase); - if(typeModelElement!==null) { - typeModelElement.astElement = it; - structType.ownedMembers += typeModelElement - } - ] - - structType.astElement = objectLiteral; - objectLiteral.definedType = structType; - - target.containingModule.internalTypes += structType - } - - // TODO GH-1337 add support for spread operator - private def dispatch TStructMember createTypeModelElement(TStructuralType structType, ObjectLiteral objectLiteral, PropertyAssignment assignment, BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - } - - /** - * Creates a TStructField. - */ - private def dispatch TStructField createTypeModelElement(TStructuralType structType, ObjectLiteral objectLiteral, PropertyNameValuePair nameValuePair, BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - val TStructField field = TypesFactory.eINSTANCE.createTStructField - field.setMemberName(nameValuePair); - field.optional = nameValuePair.declaredOptional; - if (nameValuePair.declaredTypeRefInAST !== null) { - if (!preLinkingPhase) { - field.typeRef = TypeUtils.copyWithProxies(nameValuePair.declaredTypeRefInAST) - } - } - else if(nameValuePair.expression !== null) { - field.typeRef = TypeUtils.createDeferredTypeRef; - } - else { - field.typeRef = builtInTypeScope.anyTypeRef; // FIXME inconsistent with getter/setter case; should use DeferredTypeRef also if expression===null - } -// else { -// // in all other cases: -// // leave it to the corresponding xsemantics rule to infer the type (e.g. from the initializer expression, if given) -// if(!preLinkingPhase) { -// field.typeRef = TypeUtils.createComputedTypeRef([resolveAllComputedTypeRefsInTStructuralType(structType,objectLiteral,builtInTypeScope)]) -// } -// } - field.astElement = nameValuePair; - nameValuePair.definedField = field; - return field - } - - /** - * Creates a TStructGetter. - */ - private def dispatch TStructGetter createTypeModelElement(TStructuralType structType, ObjectLiteral objectLiteral, PropertyGetterDeclaration getterDecl, BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - val TStructGetter getter = TypesFactory.eINSTANCE.createTStructGetter - getter.setMemberName(getterDecl); - getter.optional = getterDecl.declaredOptional; - if (getterDecl.declaredTypeRefInAST !== null) { - if (!preLinkingPhase) { - getter.typeRef = TypeUtils.copyWithProxies(getterDecl.declaredTypeRefInAST) - } - } else { - getter.typeRef = TypeUtils.createDeferredTypeRef; - } - getter.astElement = getterDecl; - getterDecl.definedGetter = getter; - return getter - } - - /** - * Creates a TStructSetter. - */ - private def dispatch TStructSetter createTypeModelElement(TStructuralType structType, ObjectLiteral objectLiteral, PropertySetterDeclaration setterDecl, BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - val TStructSetter setter = TypesFactory.eINSTANCE.createTStructSetter - setter.setMemberName(setterDecl); - setter.optional = setterDecl.declaredOptional; - // IMPORTANT: do not create the formal parameter with N4JSFormalParameterTypesBuilder#createFormalParameter() - // because we here use improved type inference (the type of a getter/setter in an object literal is inferred - // similarly to that of a name/value pair) - val param = TypesFactory.eINSTANCE.createTFormalParameter - if (setterDecl.fpar !== null) { - param.name = setterDecl.fpar.name - val fparDeclTypeRef = setterDecl.fpar.declaredTypeRefInAST; - if(fparDeclTypeRef!==null) { - if (!preLinkingPhase) { - param.typeRef = TypeUtils.copyWithProxies(fparDeclTypeRef); - } - } else { - param.typeRef = TypeUtils.createDeferredTypeRef; - } - param.astElement = setterDecl.fpar; - setterDecl.fpar.definedVariable = param; - } else { - // broken AST - param.typeRef = TypeUtils.createDeferredTypeRef; - // (note: using UnknownTypeRef would make more sense, but PolyComputer expects this, because - // setterDecl.declaredTypeRef===setterDecl?.fpar.declaredTypeRef===null, so setterDecl will be poly) - } - setter.fpar = param - setter.astElement = setterDecl; - setterDecl.definedSetter = setter; - return setter - } - - /** - * Creates a TStructMethod. - */ - private def dispatch TStructMethod createTypeModelElement(TStructuralType structType, ObjectLiteral objectLiteral, PropertyMethodDeclaration methodDecl, BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - val TStructMethod result = TypesFactory.eINSTANCE.createTStructMethod; - result.setMemberName(methodDecl); - // IMPORTANT: do not create the formal parameters as above for the property setters but instead create them with - // method N4JSFormalParameterTypesBuilder#createFormalParameter() (for consistency with methods in classes) - result.fpars += methodDecl.fpars.map[createFormalParameter(builtInTypeScope, preLinkingPhase)]; - if (methodDecl.declaredReturnTypeRefInAST !== null) { - if (!preLinkingPhase) { - result.returnTypeRef = TypeUtils.copyWithProxies(methodDecl.declaredReturnTypeRefInAST); - } - } else { - result.returnTypeRef = builtInTypeScope.voidTypeRef; - } - result.astElement = methodDecl; - methodDecl.definedType = result; - return result; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSSetterTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSSetterTypesBuilder.java new file mode 100644 index 0000000000..f8724b3843 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSSetterTypesBuilder.java @@ -0,0 +1,112 @@ +/** + * 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.typesbuilder; + +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.n4JS.N4SetterDeclaration; +import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TFormalParameter; +import org.eclipse.n4js.ts.types.TSetter; +import org.eclipse.n4js.ts.types.TypesFactory; + +import com.google.inject.Inject; + +/** + */ +class N4JSSetterTypesBuilder { + + @Inject + N4JSTypesBuilderHelper _n4JSTypesBuilderHelper; + @Inject + N4JSFormalParameterTypesBuilder _n4JSFormalParameterTypesBuilder; + @Inject + N4JSVariableStatementTypesBuilder _n4JSVariableStatementTypesBuilder; + + boolean canCreate(N4SetterDeclaration n4Setter) { + return n4Setter.getName() != null || n4Setter.hasComputedPropertyName(); + } + + boolean relinkSetter(N4SetterDeclaration n4Setter, TSetter tSetter, + @SuppressWarnings("unused") boolean preLinkingPhase) { + if (!canCreate(n4Setter)) { + return false; + } + + _n4JSTypesBuilderHelper.ensureEqualName(n4Setter, tSetter); + linkFormalParameters(tSetter, n4Setter); + + tSetter.setAstElement(n4Setter); + n4Setter.setDefinedSetter(tSetter); + return true; + } + + TSetter createSetter(N4SetterDeclaration n4Setter, @SuppressWarnings("unused") TClassifier classifierType, + AbstractNamespace target, + boolean preLinkingPhase) { + if (!canCreate(n4Setter)) { + return null; + } + + BuiltInTypeScope builtInTypeScope = BuiltInTypeScope.get(n4Setter.eResource().getResourceSet()); + _n4JSVariableStatementTypesBuilder.createImplicitArgumentsVariable(n4Setter, target, builtInTypeScope, + preLinkingPhase); + + TSetter setterType = TypesFactory.eINSTANCE.createTSetter(); + _n4JSTypesBuilderHelper.setMemberName(setterType, n4Setter); + setterType.setDeclaredAbstract(n4Setter.isAbstract()); + setterType.setDeclaredStatic(n4Setter.isDeclaredStatic()); + setterType.setDeclaredFinal(n4Setter.isDeclaredFinal()); + setterType.setOptional(n4Setter.isOptional()); + setterType.setDeclaredOverride(AnnotationDefinition.OVERRIDE.hasAnnotation(n4Setter)); + + setterType.setHasNoBody(n4Setter.getBody() == null + && !AnnotationDefinition.PROVIDES_DEFAULT_IMPLEMENTATION.hasAnnotation(n4Setter)); + + setMemberAccessModifier(setterType, n4Setter); + addFormalParameters(setterType, n4Setter, builtInTypeScope, preLinkingPhase); + _n4JSTypesBuilderHelper.setDeclaredThisTypeFromAnnotation(setterType, n4Setter, preLinkingPhase); + + _n4JSTypesBuilderHelper.copyAnnotations(setterType, n4Setter, preLinkingPhase); + + setterType.setAstElement(n4Setter); + n4Setter.setDefinedSetter(setterType); + return setterType; + } + + private void setMemberAccessModifier(TSetter setterType, N4SetterDeclaration n4Setter) { + _n4JSTypesBuilderHelper.setMemberAccessModifier( + modifier -> setterType.setDeclaredMemberAccessModifier(modifier), + n4Setter.getDeclaredModifiers(), n4Setter.getAnnotations()); + } + + private void addFormalParameters(TSetter setterType, N4SetterDeclaration n4Setter, + BuiltInTypeScope builtInTypeScope, + boolean preLinkingPhase) { + if (n4Setter.getFpar() != null) { + setterType.setFpar(_n4JSFormalParameterTypesBuilder.createFormalParameter(n4Setter.getFpar(), + builtInTypeScope, preLinkingPhase)); + } + } + + private boolean linkFormalParameters(TSetter setterType, N4SetterDeclaration n4Setter) { + if (n4Setter.getFpar() == null) { + return false; + } + TFormalParameter formalParameterType = setterType.getFpar(); + _n4JSTypesBuilderHelper.ensureEqualName(n4Setter.getFpar(), formalParameterType); + formalParameterType.setAstElement(n4Setter.getFpar()); + n4Setter.getFpar().setDefinedVariable(formalParameterType); + return true; + } + +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSSetterTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSSetterTypesBuilder.xtend deleted file mode 100644 index 5f39050ce7..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSSetterTypesBuilder.xtend +++ /dev/null @@ -1,99 +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.typesbuilder - -import com.google.inject.Inject -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.n4JS.N4SetterDeclaration -import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.MemberAccessModifier -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TSetter -import org.eclipse.n4js.ts.types.TypesFactory - -/** - */ -package class N4JSSetterTypesBuilder { - - @Inject extension N4JSTypesBuilderHelper - @Inject extension N4JSFormalParameterTypesBuilder - @Inject extension N4JSVariableStatementTypesBuilder - - def boolean canCreate(N4SetterDeclaration n4Setter) { - return n4Setter.name !== null || n4Setter.hasComputedPropertyName; - } - - def package boolean relinkSetter(N4SetterDeclaration n4Setter, TSetter tSetter, boolean preLinkingPhase) { - if (!canCreate(n4Setter)) { - return false - } - - ensureEqualName(n4Setter, tSetter); - tSetter.linkFormalParameters(n4Setter, preLinkingPhase) - - tSetter.astElement = n4Setter - n4Setter.definedSetter = tSetter - return true - } - - def package TSetter createSetter(N4SetterDeclaration n4Setter, TClassifier classifierType, AbstractNamespace target, boolean preLinkingPhase) { - if (!canCreate(n4Setter)) { - return null - } - - val builtInTypeScope = BuiltInTypeScope.get(n4Setter.eResource.resourceSet) - n4Setter.createImplicitArgumentsVariable(target, builtInTypeScope, preLinkingPhase); - - val setterType = TypesFactory::eINSTANCE.createTSetter - setterType.setMemberName(n4Setter); - setterType.declaredAbstract = n4Setter.abstract - setterType.declaredStatic = n4Setter.declaredStatic - setterType.declaredFinal = n4Setter.declaredFinal - setterType.optional = n4Setter.optional; - setterType.declaredOverride = AnnotationDefinition.OVERRIDE.hasAnnotation(n4Setter); - - setterType.hasNoBody = n4Setter.body===null && !AnnotationDefinition.PROVIDES_DEFAULT_IMPLEMENTATION.hasAnnotation(n4Setter); - - setterType.setMemberAccessModifier(n4Setter) - setterType.addFormalParameters(n4Setter, builtInTypeScope, preLinkingPhase) - setterType.setDeclaredThisTypeFromAnnotation(n4Setter, preLinkingPhase) - - setterType.copyAnnotations(n4Setter, preLinkingPhase) - - setterType.astElement = n4Setter - n4Setter.definedSetter = setterType - setterType; - } - - def private void setMemberAccessModifier(TSetter setterType, N4SetterDeclaration n4Setter) { - setMemberAccessModifier([MemberAccessModifier modifier|setterType.declaredMemberAccessModifier = modifier], - n4Setter.declaredModifiers, n4Setter.annotations) - } - - def private void addFormalParameters(TSetter setterType, N4SetterDeclaration n4Setter, BuiltInTypeScope builtInTypeScope, - boolean preLinkingPhase) { - if (n4Setter.fpar !== null) - setterType.fpar = n4Setter.fpar.createFormalParameter(builtInTypeScope, preLinkingPhase); - } - - def private boolean linkFormalParameters(TSetter setterType, N4SetterDeclaration n4Setter, boolean preLinkingPhase) { - if (n4Setter.fpar === null) { - return false; - } - val formalParameterType = setterType.fpar; - ensureEqualName(n4Setter.fpar, formalParameterType); - formalParameterType.astElement = n4Setter.fpar; - n4Setter.fpar.definedVariable = formalParameterType; - return true; - } - -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypeAliasDeclarationTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypeAliasDeclarationTypesBuilder.java new file mode 100644 index 0000000000..227440dc8e --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypeAliasDeclarationTypesBuilder.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2021 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.typesbuilder; + +import org.eclipse.n4js.n4JS.N4TypeAliasDeclaration; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.TypeAlias; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.types.utils.TypeUtils; + +import com.google.inject.Inject; + +class N4JSTypeAliasDeclarationTypesBuilder { + + @Inject + N4JSTypeVariableTypesBuilder _n4JSTypeVariableTypesBuilder; + @Inject + N4JSTypesBuilderHelper _n4JSTypesBuilderHelper; + + protected boolean relinkTypeAlias(N4TypeAliasDeclaration n4TypeAlias, AbstractNamespace target, + @SuppressWarnings("unused") boolean preLinkingPhase, int idx) { + + if (n4TypeAlias.getName() == null) { // may be null due to syntax errors + return false; + } + + TypeAlias typeAlias = (TypeAlias) target.getTypes().get(idx); + + _n4JSTypesBuilderHelper.ensureEqualName(n4TypeAlias, typeAlias); + typeAlias.setAstElement(n4TypeAlias); + n4TypeAlias.setDefinedType(typeAlias); + + return true; + } + + void createTypeAlias(N4TypeAliasDeclaration n4TypeAlias, AbstractNamespace target, boolean preLinkingPhase) { + if (n4TypeAlias.getName() == null) { // may be null due to syntax errors + return; + } + + TypeAlias typeAlias = TypesFactory.eINSTANCE.createTypeAlias(); + typeAlias.setName(n4TypeAlias.getName()); + + _n4JSTypesBuilderHelper.setTypeAccessModifier(typeAlias, n4TypeAlias); + _n4JSTypeVariableTypesBuilder.addTypeParameters(typeAlias, n4TypeAlias, preLinkingPhase); + + _n4JSTypesBuilderHelper.copyAnnotations(typeAlias, n4TypeAlias, preLinkingPhase); + + if (!preLinkingPhase) { + typeAlias.setTypeRef(TypeUtils.copyWithProxies(n4TypeAlias.getDeclaredTypeRefInAST())); + } + + typeAlias.setAstElement(n4TypeAlias); + n4TypeAlias.setDefinedType(typeAlias); + + target.getTypes().add(typeAlias); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypeAliasDeclarationTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypeAliasDeclarationTypesBuilder.xtend deleted file mode 100644 index 522844ba52..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypeAliasDeclarationTypesBuilder.xtend +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2021 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.typesbuilder - -import com.google.inject.Inject -import org.eclipse.n4js.n4JS.N4TypeAliasDeclaration -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.TypeAlias -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.types.utils.TypeUtils - -class N4JSTypeAliasDeclarationTypesBuilder { - - @Inject extension N4JSTypeVariableTypesBuilder - @Inject extension N4JSTypesBuilderHelper - - def protected boolean relinkTypeAlias(N4TypeAliasDeclaration n4TypeAlias, AbstractNamespace target, boolean preLinkingPhase, int idx) { - if (n4TypeAlias.name === null) { // may be null due to syntax errors - return false; - } - - val TypeAlias typeAlias = target.types.get(idx) as TypeAlias - - ensureEqualName(n4TypeAlias, typeAlias); - typeAlias.astElement = n4TypeAlias; - n4TypeAlias.definedType = typeAlias; - - return true; - } - - def package void createTypeAlias(N4TypeAliasDeclaration n4TypeAlias, AbstractNamespace target, boolean preLinkingPhase) { - if(n4TypeAlias.name === null) { // may be null due to syntax errors - return; - } - - val typeAlias = TypesFactory.eINSTANCE.createTypeAlias(); - typeAlias.name = n4TypeAlias.name; - - typeAlias.setTypeAccessModifier(n4TypeAlias); - typeAlias.addTypeParameters(n4TypeAlias, preLinkingPhase); - - typeAlias.copyAnnotations(n4TypeAlias, preLinkingPhase); - - if (!preLinkingPhase) { - typeAlias.typeRef = TypeUtils.copyWithProxies(n4TypeAlias.declaredTypeRefInAST); - } - - typeAlias.astElement = n4TypeAlias; - n4TypeAlias.definedType = typeAlias; - - target.types += typeAlias; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypeVariableTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypeVariableTypesBuilder.java new file mode 100644 index 0000000000..d73c54dec7 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypeVariableTypesBuilder.java @@ -0,0 +1,84 @@ +/** + * 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.typesbuilder; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.n4js.n4JS.GenericDeclaration; +import org.eclipse.n4js.n4JS.N4TypeVariable; +import org.eclipse.n4js.n4JS.TypeReferenceNode; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.GenericType; +import org.eclipse.n4js.ts.types.TypeVariable; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions; + +import com.google.inject.Inject; + +class N4JSTypeVariableTypesBuilder { + + @Inject + N4JSTypesBuilderHelper _n4JSTypesBuilderHelper; + + void relinkTypeParameters(GenericDeclaration decl, GenericType target) { + EList n4TypeVars = decl.getTypeVars(); + EList typeVars = target.getTypeVars(); + int len = Math.min(n4TypeVars.size(), typeVars.size()); + for (var i = 0; i < len; i++) { + relinkTypeParameter(n4TypeVars.get(i), typeVars.get(i)); + } + } + + private void relinkTypeParameter(N4TypeVariable n4TypeVar, TypeVariable typeVar) { + if (n4TypeVar == null || typeVar == null) { + return; + } + _n4JSTypesBuilderHelper.ensureEqualName(n4TypeVar, typeVar); + n4TypeVar.setDefinedTypeVariable(typeVar); + } + + void addTypeParameters(GenericType target, GenericDeclaration decl, boolean preLinkingPhase) { + target.getTypeVars().clear(); + target.getTypeVars().addAll(toList(map(decl.getTypeVars(), tv -> createTypeVariable(tv, preLinkingPhase)))); + } + + private TypeVariable createTypeVariable(N4TypeVariable n4TypeVar, boolean preLinkingPhase) { + TypeVariable typeVar = TypesFactory.eINSTANCE.createTypeVariable(); + typeVar.setName(n4TypeVar.getName()); + typeVar.setDeclaredCovariant(n4TypeVar.isDeclaredCovariant()); + typeVar.setDeclaredContravariant(n4TypeVar.isDeclaredContravariant()); + if (!preLinkingPhase) { + TypeReferenceNode bound = n4TypeVar.getDeclaredUpperBoundNode(); + typeVar.setDeclaredUpperBound(TypeUtils.copyWithProxies(bound == null ? null : bound.getTypeRefInAST())); + if (n4TypeVar.isOptional()) { + TypeReferenceNode argumentNode = n4TypeVar.getDeclaredDefaultArgumentNode(); + TypeRef typeRef = argumentNode == null ? null : argumentNode.getTypeRefInAST(); + if (typeRef == null) { + if (typeVar.getDeclaredUpperBound() == null) { + RuleEnvironment G = RuleEnvironmentExtensions.newRuleEnvironment(n4TypeVar); + typeRef = RuleEnvironmentExtensions.anyTypeRef(G); + } else { + typeRef = typeVar.getDeclaredUpperBound(); + } + } + typeVar.setDefaultArgument(TypeUtils.copyWithProxies(typeRef)); + } + } + + n4TypeVar.setDefinedTypeVariable(typeVar); + + return typeVar; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypeVariableTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypeVariableTypesBuilder.xtend deleted file mode 100644 index 321ead810f..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypeVariableTypesBuilder.xtend +++ /dev/null @@ -1,74 +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.typesbuilder - -import com.google.inject.Inject -import org.eclipse.n4js.n4JS.GenericDeclaration -import org.eclipse.n4js.n4JS.N4TypeVariable -import org.eclipse.n4js.ts.types.GenericType -import org.eclipse.n4js.ts.types.TypeVariable -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.types.utils.TypeUtils - -import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions - -package class N4JSTypeVariableTypesBuilder { - - @Inject extension N4JSTypesBuilderHelper - - def package void relinkTypeParameters(GenericDeclaration decl, GenericType target) { - val n4TypeVars = decl.typeVars; - val typeVars = target.typeVars; - val len = Math.min(n4TypeVars.size, typeVars.size); - for (var i = 0; i < len; i++) { - relinkTypeParameter(n4TypeVars.get(i), typeVars.get(i)); - } - } - - def private void relinkTypeParameter(N4TypeVariable n4TypeVar, TypeVariable typeVar) { - if (n4TypeVar === null || typeVar === null) { - return; - } - ensureEqualName(n4TypeVar, typeVar); - n4TypeVar.definedTypeVariable = typeVar; - } - - def package void addTypeParameters(GenericType target, GenericDeclaration decl, boolean preLinkingPhase) { - target.typeVars.clear(); - target.typeVars += decl.typeVars.map[createTypeVariable(it, preLinkingPhase)]; - } - - def private TypeVariable createTypeVariable(N4TypeVariable n4TypeVar, boolean preLinkingPhase) { - val typeVar = TypesFactory.eINSTANCE.createTypeVariable(); - typeVar.name = n4TypeVar.name; - typeVar.declaredCovariant = n4TypeVar.declaredCovariant; - typeVar.declaredContravariant = n4TypeVar.declaredContravariant; - if (!preLinkingPhase) { - typeVar.declaredUpperBound = TypeUtils.copyWithProxies(n4TypeVar.declaredUpperBoundNode?.typeRefInAST); - if (n4TypeVar.isOptional) { - var typeRef = n4TypeVar.getDeclaredDefaultArgumentNode?.typeRefInAST; - if (typeRef === null) { - if (typeVar.declaredUpperBound === null) { - val G = RuleEnvironmentExtensions.newRuleEnvironment(n4TypeVar); - typeRef = RuleEnvironmentExtensions.anyTypeRef(G); - } else { - typeRef = typeVar.declaredUpperBound; - } - } - typeVar.defaultArgument = TypeUtils.copyWithProxies(typeRef); - } - } - - n4TypeVar.definedTypeVariable = typeVar; - - return typeVar; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesBuilder.java new file mode 100644 index 0000000000..3a1ca0f5f4 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesBuilder.java @@ -0,0 +1,685 @@ +/** + * 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.typesbuilder; + +import static org.eclipse.n4js.utils.N4JSLanguageUtils.isContainedInStaticPolyfillAware; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.isContainedInStaticPolyfillModule; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.toList; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.n4JS.ExportDeclaration; +import org.eclipse.n4js.n4JS.ExportableElement; +import org.eclipse.n4js.n4JS.FunctionDeclaration; +import org.eclipse.n4js.n4JS.FunctionDefinition; +import org.eclipse.n4js.n4JS.FunctionExpression; +import org.eclipse.n4js.n4JS.MethodDeclaration; +import org.eclipse.n4js.n4JS.ModifiableElement; +import org.eclipse.n4js.n4JS.N4ClassDeclaration; +import org.eclipse.n4js.n4JS.N4ClassExpression; +import org.eclipse.n4js.n4JS.N4EnumDeclaration; +import org.eclipse.n4js.n4JS.N4InterfaceDeclaration; +import org.eclipse.n4js.n4JS.N4JSASTUtils; +import org.eclipse.n4js.n4JS.N4Modifier; +import org.eclipse.n4js.n4JS.N4NamespaceDeclaration; +import org.eclipse.n4js.n4JS.N4TypeAliasDeclaration; +import org.eclipse.n4js.n4JS.NamespaceExportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.ObjectLiteral; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.n4JS.TryStatement; +import org.eclipse.n4js.n4JS.TypeDefiningElement; +import org.eclipse.n4js.n4JS.VariableDeclarationContainer; +import org.eclipse.n4js.naming.ModuleNameComputer; +import org.eclipse.n4js.naming.SpecifierConverter; +import org.eclipse.n4js.resource.N4JSResource; +import org.eclipse.n4js.smith.Measurement; +import org.eclipse.n4js.smith.N4JSDataCollectors; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExprOrRef; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.StructuralTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.TInterface; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.ts.types.TVariable; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.utils.ResourceType; +import org.eclipse.n4js.validation.JavaScriptVariantHelper; +import org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot; +import org.eclipse.n4js.workspace.WorkspaceAccess; +import org.eclipse.xtext.naming.IQualifiedNameConverter; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.parser.IParseResult; +import org.eclipse.xtext.resource.DerivedStateAwareResource; +import org.eclipse.xtext.xbase.lib.ListExtensions; + +import com.google.inject.Inject; + +/** + * This class with its {@link N4JSTypesBuilder#createTModuleFromSource(DerivedStateAwareResource,boolean) + * createTModuleFromSource()} method is the single entry point for the types builder package. The only exception is the + * public method + * {@link N4JSFunctionDefinitionTypesBuilder#updateTFunction(FunctionExpression, FunctionTypeExprOrRef, TypeRef) + * updateTFunction()} in N4JSFunctionDefinitionTypesBuilder, which is called from Xsemantics. + *

+ * Derives the types model from the AST, i.e. creates a {@link TModule} with its contents from a {@link Script} and its + * children. The types model is stored at the second index of the resource. The types model contains for each exportable + * or type defining element a corresponding entry e.g. for a {@link N4ClassDeclaration} a {@link TClass} is created. + * Later when linking only the types are used in place of the original objects. + *

+ * The types builder must not use type inference because this would lead to a resolution of lazy cross-references at a + * too early stage. Instead, the types builder creates ComputedTypeRefs that will later be resolved either on demand or + * by calling {@link N4JSResource#flattenModule()}. + */ +@SuppressWarnings({ "javadoc", "unused" }) +public class N4JSTypesBuilder { + + @Inject(optional = true) + TypesFactory typesFactory = TypesFactory.eINSTANCE; + @Inject + N4JSTypesBuilderHelper _n4JSTypesBuilderHelper; + @Inject + N4JSNamespaceDeclarationTypesBuilder _n4JSNamespaceDeclarationTypesBuilder; + @Inject + N4JSClassDeclarationTypesBuilder _n4JSClassDeclarationTypesBuilder; + @Inject + N4JSInterfaceDeclarationTypesBuilder _n4JSInterfaceDeclarationTypesBuilder; + @Inject + N4JSEnumDeclarationTypesBuilder _n4JSEnumDeclarationTypesBuilder; + @Inject + N4JSTypeAliasDeclarationTypesBuilder _n4JSTypeAliasDeclarationTypesBuilder; + @Inject + N4JSObjectLiteralTypesBuilder _n4JSObjectLiteralTypesBuilder; + @Inject + N4JSFunctionDefinitionTypesBuilder _n4JSFunctionDefinitionTypesBuilder; + @Inject + N4JSVariableStatementTypesBuilder _n4JSVariableStatementTypesBuilder; + @Inject + N4JSTypesFromTypeRefBuilder _n4JSTypesFromTypeRefBuilder; + @Inject + N4JSImportTypesBuilder _n4JSImportTypesBuilder; + @Inject + N4JSExportDefinitionTypesBuilder _n4JSExportDefinitionTypesBuilder; + + @Inject + ModuleNameComputer _moduleNameComputer; + @Inject + private WorkspaceAccess workspaceAccess; + @Inject + private IQualifiedNameConverter qualifiedNameConverter; + @Inject + private SpecifierConverter specifierConverter; + @Inject + protected JavaScriptVariantHelper jsVariantHelper; + + /** Thrown when reconciliation of a TModule fails due to a hash mismatch. */ + public static class RelinkTModuleHashMismatchException extends IllegalStateException { + public RelinkTModuleHashMismatchException(URI resourceURI) { + super("cannot link existing TModule to new AST due to hash mismatch: " + resourceURI); + } + } + + /** + * When demand-loading an AST for a resource that already has a TModule (usually retrieved from the index) by + * calling SyntaxRelatedTElement#getAstElement(), we are facing a challenge: we could simply replace the original + * TModule by a new TModule created in the same way as in the standard case of loading an empty resource from + * source, i.e. with method {@link N4JSTypesBuilder#createTModuleFromSource(DerivedStateAwareResource, boolean) + * #createTModuleFromSource()}. However, this would lead to the issue of all existing references to the original, + * now replaced TModule to now have an invalid target object (not contained in a resource anymore, proxy resolution + * impossible, etc.). + *

+ * As a solution, this method provides a 2nd mode of the types builder in which not a new TModule is created from + * the AST, but an existing TModule is reused, i.e. the types builder does not create anything but simply creates + * the bidirectional links between AST nodes and TModule elements. + *

+ * This method should be called after the AST has been loaded, with the original TModule at second position in the + * resource's contents. If the AST and TModule were created from different versions of the source, checked via an + * MD5 hash, or the rewiring fails for other reasons, an {@link IllegalStateException} is thrown. In that case, the + * state of the AST and TModule are undefined (i.e. linking may have taken place partially). + */ + public void relinkTModuleToSource(DerivedStateAwareResource resource, boolean preLinkingPhase) { + try (Measurement m1 = N4JSDataCollectors.dcTypesBuilder.getMeasurement(); + Measurement m2 = N4JSDataCollectors.dcTypesBuilderRelink.getMeasurement()) { + m1.getClass(); // suppress unused variable warning from Xtend + m2.getClass(); // suppress unused variable warning from Xtend + + doRelinkTModuleToSource(resource, preLinkingPhase); + } + } + + private void doRelinkTModuleToSource(DerivedStateAwareResource resource, boolean preLinkingPhase) { + IParseResult parseResult = resource.getParseResult(); + if (parseResult != null) { + + Script script = (Script) parseResult.getRootASTElement(); + + TModule module = (TModule) resource.getContents().get(1); + + String astMD5New = N4JSASTUtils.md5Hex(resource); + if (!Objects.equals(astMD5New, module.getAstMD5())) { + throw new RelinkTModuleHashMismatchException(resource.getURI()); + } + module.setReconciled(true); + + _n4JSImportTypesBuilder.relinkTypeModelElementsForImports(script, module, preLinkingPhase); + + buildTypesFromTypeRefs(script, module, preLinkingPhase); + + relinkTypes(script, module, preLinkingPhase, new RelinkIndices()); + + module.setAstElement(script); + script.setModule(module); + + } else { + throw new IllegalStateException("resource has no parse result: " + resource.getURI()); + } + } + + /** + * This method is the single entry point for the types builder package. The only exception is the public method + * {@link N4JSFunctionDefinitionTypesBuilder#updateTFunction(FunctionExpression,FunctionTypeExprOrRef,TypeRef) + * updateTFunction()} in N4JSFunctionDefinitionTypesBuilder, which is called from Xsemantics. + *

+ * Creates an {@link TModule} with the module path name (@see {@link ModuleNameComputer#getModulePath(Resource)}) of + * the resource (replacing the dots with slashes). For all {@link ExportableElement} and {@link TypeDefiningElement} + * corresponding types like {@link TClass}, {@link TInterface} are created and assigned as containment references to + * {@link TModule}. Afterwards the so created {@link TModule} tree is browsed for {@link TVariable}s which do not + * have a type reference yet. For these there right hand side expressions is checked for a + * {@link TypeDefiningElement} for this a {@link ParameterizedTypeRef} is created having this element as declared + * type. The parameterized type ref is then assigned as type ref to the {@link TVariable}. + */ + public void createTModuleFromSource(DerivedStateAwareResource resource, boolean preLinkingPhase) { + try (Measurement m1 = N4JSDataCollectors.dcTypesBuilder.getMeasurement(); + Measurement m2 = N4JSDataCollectors.dcTypesBuilderCreate.getMeasurement()) { + m1.getClass(); // suppress unused variable warning from Xtend + m2.getClass(); // suppress unused variable warning from Xtend + + doCreateTModuleFromSource(resource, preLinkingPhase); + } + } + + private void doCreateTModuleFromSource(DerivedStateAwareResource resource, boolean preLinkingPhase) { + IParseResult parseResult = resource.getParseResult(); + if (parseResult != null) { + + // UtilN4.takeSnapshotInGraphView("TB start (preLinking=="+preLinkingPhase+")",resource.resourceSet); + Script script = (Script) parseResult.getRootASTElement(); + + TModule result = typesFactory.createTModule(); + + result.setAstMD5(N4JSASTUtils.md5Hex(resource)); + result.setReconciled(false); + + QualifiedName qualifiedModuleName = _moduleNameComputer.getQualifiedModuleName(resource); + result.setSimpleName(qualifiedModuleName.getLastSegment()); + result.setQualifiedName(qualifiedNameConverter.toString(qualifiedModuleName)); + result.setPreLinkingPhase(preLinkingPhase); + + N4JSProjectConfigSnapshot project = workspaceAccess.findProjectContaining(resource); + if (project != null) { + result.setProjectID(project.getName()); + result.setPackageName(project.getPackageName()); + result.setVendorID(project.getVendorId()); + + // main module + String mainModuleSpec = project.getMainModule(); + if (mainModuleSpec != null) { + result.setMainModule( + Objects.equals(qualifiedModuleName, specifierConverter.toQualifiedName(mainModuleSpec))); + } + } + + _n4JSTypesBuilderHelper.copyAnnotations(result, script, preLinkingPhase); + + _n4JSImportTypesBuilder.createTypeModelElementsForImports(script, result, preLinkingPhase); + + result.setN4jsdModule(jsVariantHelper.isExternalMode(script)); + + // Setting Polyfill property. + result.setStaticPolyfillModule(isContainedInStaticPolyfillModule(result)); + result.setStaticPolyfillAware(isContainedInStaticPolyfillAware(result)); + + buildTypesFromTypeRefs(script, result, preLinkingPhase); + + buildTypes(script, result, preLinkingPhase); + + result.setAstElement(script); + script.setModule(result); + ((N4JSResource) resource).sneakyAddToContent(result); + // UtilN4.takeSnapshotInGraphView("TB end (preLinking=="+preLinkingPhase+")",resource.resourceSet); + } else { + throw new IllegalStateException(resource.getURI() + " has no parse result."); + } + } + + /** + * Create types for those TypeRefs that define a type if they play the role of an AST node. + *

+ * This has to be done up-front, because in the rest of the types builder code we do not know where such a TypeRef + * shows up; to avoid having to check for them at every occurrence of a TypeRef, we do this ahead of the main types + * builder phase. + */ + private void buildTypesFromTypeRefs(Script script, TModule target, boolean preLinkingPhase) { + if (!preLinkingPhase) { + // important to do the following in bottom-up order! + // The structural members of a higher-level StructuralTypeRef STR may contain another StructuralTypeRef + // STR'; + // when the TStructuralType of STR is created, the structural members are copied, including contained + // TypeRefs; + // at that time the lower-level STR' must already have been prepared for copying! + // Example: + // var ~Object with { + // ~Object with { number fieldOfField; } field; + // } x; + List addTypesToTargets = new ArrayList<>(); + for (TypeRef tr : ListExtensions.reverseView(toList(filter(script.eAllContents(), TypeRef.class)))) { + if (tr instanceof StructuralTypeRef) { + StructuralTypeRef str = (StructuralTypeRef) tr; + _n4JSTypesFromTypeRefBuilder.createStructuralType(str); + if (str.getStructuralType() != null) { + addTypesToTargets.add(str.getStructuralType()); + } + } else if (tr instanceof FunctionTypeExpression) { + FunctionTypeExpression ftr = (FunctionTypeExpression) tr; + _n4JSTypesFromTypeRefBuilder.createTFunction(ftr); + if (ftr.getDeclaredType() != null) { + addTypesToTargets.add(ftr.getDeclaredType()); + } + } else if (tr instanceof N4NamespaceDeclaration) { + for (Type type : addTypesToTargets) { + target.getInternalTypes().add(type); + } + addTypesToTargets.clear(); + } + } + + for (Type type : addTypesToTargets) { + target.getInternalTypes().add(type); + } + } + } + + static class RelinkIndices { + int namespacesIdx = 0; + int typesIdx = 0; + int functionIdx = 0; + int variableIdx = 0; + } + + private void relinkTypes(EObject container, AbstractNamespace target, boolean preLinkingPhase, RelinkIndices rlis) { + for (EObject n : container.eContents()) { + // relink types of n (if applicable) + + if (n instanceof N4NamespaceDeclaration) { + rlis.namespacesIdx = relinkNamespace((N4NamespaceDeclaration) n, target, preLinkingPhase, + rlis.namespacesIdx); + } else if (n instanceof FunctionDefinition) { + rlis.functionIdx = relinkFunction(n, target, preLinkingPhase, rlis.functionIdx); + } else if (n instanceof TypeDefiningElement) { + rlis.typesIdx = relinkType(n, target, preLinkingPhase, rlis.typesIdx); + } else if (n instanceof VariableDeclarationContainer) { + rlis.variableIdx = relinkType(n, target, preLinkingPhase, + rlis.variableIdx); + } else if (n instanceof TryStatement) { + rlis.variableIdx = relinkType(n, target, preLinkingPhase, rlis.variableIdx); + } + + // relink types of child nodes + AbstractNamespace nextTarget; + RelinkIndices nextRelinkIndices; + if (n instanceof N4NamespaceDeclaration) { + nextTarget = ((N4NamespaceDeclaration) n).getDefinedNamespace(); + nextRelinkIndices = new RelinkIndices(); + } else { + nextTarget = target; + nextRelinkIndices = rlis; + } + if (nextTarget != null) { // can be null in broken ASTs + relinkTypes(n, nextTarget, preLinkingPhase, nextRelinkIndices); + } + // handle exports + // -> nothing to do + // (AST nodes of type ExportDeclaration are not TypeDefiningElements and + // type model elements of type ExportDefinition are not SyntaxRelatedTElements, + // so we do not have to relink anything between AST and types model here) + } + } + + protected int relinkNamespace(N4NamespaceDeclaration n4Namespace, AbstractNamespace target, boolean preLinkingPhase, + int idx) { + if (_n4JSNamespaceDeclarationTypesBuilder.relinkTNamespace(n4Namespace, target, preLinkingPhase, idx)) { + return idx + 1; + } + return idx; + } + + protected int relinkType(TypeDefiningElement other, AbstractNamespace target, boolean preLinkingPhase, int idx) { + throw new IllegalArgumentException( + "unknown subclass of TypeDefiningElement: " + (other == null ? null : other.eClass().getName())); + } + + protected int relinkType(NamespaceImportSpecifier nsImpSpec, AbstractNamespace target, boolean preLinkingPhase, + int idx) { + // already handled up-front in N4JSNamespaceImportTypesBuilder#relinkNamespaceTypes + return idx; + } + + protected int relinkFunction(MethodDeclaration n4MethodDecl, AbstractNamespace target, boolean preLinkingPhase, + int idx) { + // methods are handled in their containing class/interface -> ignore them here + return idx; + } + + protected int relinkFunction(FunctionDeclaration n4FunctionDecl, AbstractNamespace target, boolean preLinkingPhase, + int idx) { + if (_n4JSFunctionDefinitionTypesBuilder.relinkTFunction(n4FunctionDecl, target, preLinkingPhase, idx)) { + return idx + 1; + } + return idx; + } + + /** + * Function expressions are special, see + * {@link N4JSFunctionDefinitionTypesBuilder#createTFunction(FunctionExpression,TModule,boolean)}. + */ + protected int relinkFunction(FunctionExpression n4FunctionExpr, AbstractNamespace target, boolean preLinkingPhase, + int idx) { + _n4JSFunctionDefinitionTypesBuilder.createTFunction(n4FunctionExpr, target, preLinkingPhase); + return idx; + } + + protected int relinkType(N4ClassDeclaration n4Class, AbstractNamespace target, boolean preLinkingPhase, int idx) { + if (_n4JSClassDeclarationTypesBuilder.relinkTClass(n4Class, target, preLinkingPhase, idx)) { + return idx + 1; + } + return idx; + } + + protected int relinkType(N4ClassExpression n4Class, AbstractNamespace target, boolean preLinkingPhase, int idx) { + _n4JSClassDeclarationTypesBuilder.createTClass(n4Class, target, preLinkingPhase); + // do not increment the index + return idx; + } + + protected int relinkType(N4InterfaceDeclaration n4Interface, AbstractNamespace target, boolean preLinkingPhase, + int idx) { + if (_n4JSInterfaceDeclarationTypesBuilder.relinkTInterface(n4Interface, target, preLinkingPhase, idx)) { + return idx + 1; + } + return idx; + } + + protected int relinkType(N4EnumDeclaration n4Enum, AbstractNamespace target, boolean preLinkingPhase, int idx) { + if (_n4JSEnumDeclarationTypesBuilder.relinkTEnum(n4Enum, target, preLinkingPhase, idx)) { + return idx + 1; + } + return idx; + } + + protected int relinkType(N4TypeAliasDeclaration n4TypeAlias, AbstractNamespace target, boolean preLinkingPhase, + int idx) { + if (_n4JSTypeAliasDeclarationTypesBuilder.relinkTypeAlias(n4TypeAlias, target, preLinkingPhase, idx)) { + return idx + 1; + } + return idx; + } + + protected int relinkType(ObjectLiteral objectLiteral, AbstractNamespace target, boolean preLinkingPhase, int idx) { + _n4JSObjectLiteralTypesBuilder.createObjectLiteral(objectLiteral, target, preLinkingPhase); + return idx; + } + + protected int relinkType(VariableDeclarationContainer n4VarDeclContainer, AbstractNamespace target, + boolean preLinkingPhase, int idx) { + return _n4JSVariableStatementTypesBuilder.relinkVariableTypes(n4VarDeclContainer, target, preLinkingPhase, idx); + } + + protected int relinkType(TryStatement tryStmnt, AbstractNamespace target, boolean preLinkingPhase, int idx) { + return _n4JSVariableStatementTypesBuilder.relinkVariableTypes(tryStmnt, target, preLinkingPhase, idx); + } + + protected int relinkType(NamespaceExportSpecifier n4NamespaceExportSpecifier, AbstractNamespace target, + boolean preLinkingPhase, int idx) { + // namespace export specifiers are handled as part of their containing ExportDeclaration -> ignore them here + return idx; + } + + private void buildTypes(EObject container, AbstractNamespace target, boolean preLinkingPhase) { + for (EObject n : container.eContents()) { + // build type for n (if applicable) + if (n instanceof N4NamespaceDeclaration) { + createNamespace((N4NamespaceDeclaration) n, target, preLinkingPhase); + } else if (n instanceof FunctionDefinition) { + createFunction(n, target, preLinkingPhase); + } else if (n instanceof TypeDefiningElement) { + createType(n, target, preLinkingPhase); + } else if (n instanceof VariableDeclarationContainer) { + // VariableStatement and ForStatement + createType(n, target, preLinkingPhase); + } else if (n instanceof TryStatement) { + createType(n, target, preLinkingPhase); + } + + // build types for child nodes + AbstractNamespace nextTarget = (n instanceof N4NamespaceDeclaration) + ? ((N4NamespaceDeclaration) n).getDefinedNamespace() + : target; + if (nextTarget != null) { // can be null in broken ASTs + buildTypes(n, nextTarget, preLinkingPhase); + } + // handle exports + if (n instanceof ExportDeclaration) { + createType(n, target, preLinkingPhase); + } else if (n instanceof ExportableElement) { + ExportableElement ee = (ExportableElement) n; + if (!ee.isDeclaredExported() && ee.isExportedByNamespace()) { + boolean isDtsExceptionCase = ResourceType.getResourceType(ee) == ResourceType.DTS + && ee instanceof ModifiableElement + && ((ModifiableElement) ee).getDeclaredModifiers().contains(N4Modifier.PRIVATE); + if (!isDtsExceptionCase) { + _n4JSExportDefinitionTypesBuilder.createExportDefinitionForDirectlyExportedElement(ee, target, + preLinkingPhase); + } + } + } + } + } + + protected void createNamespace(N4NamespaceDeclaration n4Namespace, AbstractNamespace target, + boolean preLinkingPhase) { + _n4JSNamespaceDeclarationTypesBuilder.createTNamespace(n4Namespace, target, preLinkingPhase); + } + + protected void createFunction(MethodDeclaration n4MethodDecl, AbstractNamespace target, boolean preLinkingPhase) { + // methods are handled in their containing class/interface -> ignore them here + } + + protected void createFunction(FunctionDeclaration n4FunctionDecl, AbstractNamespace target, + boolean preLinkingPhase) { + _n4JSFunctionDefinitionTypesBuilder.createTFunction(n4FunctionDecl, target, preLinkingPhase); + } + + /** + * Function expressions are special, see + * {@link N4JSFunctionDefinitionTypesBuilder#createTFunction(FunctionExpression,TModule,boolean)}. + */ + protected void createFunction(FunctionExpression n4FunctionExpr, AbstractNamespace target, + boolean preLinkingPhase) { + _n4JSFunctionDefinitionTypesBuilder.createTFunction(n4FunctionExpr, target, preLinkingPhase); + } + + protected void createType(TypeDefiningElement other, AbstractNamespace target, boolean preLinkingPhase) { + throw new IllegalArgumentException( + "unknown subclass of TypeDefiningElement: " + (other == null ? null : other.eClass().getName())); + } + + protected void createType(NamespaceImportSpecifier nsImpSpec, AbstractNamespace target, boolean preLinkingPhase) { + // already handled up-front in #buildNamespacesTypesFromModuleImports() + } + + protected void createType(N4ClassDeclaration n4Class, AbstractNamespace target, boolean preLinkingPhase) { + _n4JSClassDeclarationTypesBuilder.createTClass(n4Class, target, preLinkingPhase); + } + + protected void createType(N4ClassExpression n4Class, AbstractNamespace target, boolean preLinkingPhase) { + _n4JSClassDeclarationTypesBuilder.createTClass(n4Class, target, preLinkingPhase); + } + + protected void createType(N4InterfaceDeclaration n4Interface, AbstractNamespace target, boolean preLinkingPhase) { + _n4JSInterfaceDeclarationTypesBuilder.createTInterface(n4Interface, target, preLinkingPhase); + } + + protected void createType(N4EnumDeclaration n4Enum, AbstractNamespace target, boolean preLinkingPhase) { + _n4JSEnumDeclarationTypesBuilder.createTEnum(n4Enum, target, preLinkingPhase); + } + + protected void createType(N4TypeAliasDeclaration n4TypeAliasDecl, AbstractNamespace target, + boolean preLinkingPhase) { + _n4JSTypeAliasDeclarationTypesBuilder.createTypeAlias(n4TypeAliasDecl, target, preLinkingPhase); + } + + protected void createType(ObjectLiteral objectLiteral, AbstractNamespace target, boolean preLinkingPhase) { + _n4JSObjectLiteralTypesBuilder.createObjectLiteral(objectLiteral, target, preLinkingPhase); + } + + protected void createType(VariableDeclarationContainer n4VarDeclContainer, AbstractNamespace target, + boolean preLinkingPhase) { + _n4JSVariableStatementTypesBuilder.createVariableTypes(n4VarDeclContainer, target, preLinkingPhase); + } + + protected void createType(TryStatement tryStmnt, AbstractNamespace target, boolean preLinkingPhase) { + _n4JSVariableStatementTypesBuilder.createVariableTypes(tryStmnt, target, preLinkingPhase); + } + + protected void createType(ExportDeclaration n4ExportDeclaration, AbstractNamespace target, + boolean preLinkingPhase) { + _n4JSExportDefinitionTypesBuilder.createExportDefinition(n4ExportDeclaration, target, preLinkingPhase); + } + + protected void createType(NamespaceExportSpecifier n4NamespaceExportSpecifier, AbstractNamespace target, + boolean preLinkingPhase) { + // namespace export specifiers are handled as part of their containing ExportDeclaration -> ignore them here + } + + protected int relinkType(final EObject n4Class, final AbstractNamespace target, final boolean preLinkingPhase, + final int idx) { + if (n4Class instanceof N4ClassDeclaration) { + return relinkType((N4ClassDeclaration) n4Class, target, preLinkingPhase, idx); + } else if (n4Class instanceof N4ClassExpression) { + return relinkType((N4ClassExpression) n4Class, target, preLinkingPhase, idx); + } else if (n4Class instanceof N4InterfaceDeclaration) { + return relinkType((N4InterfaceDeclaration) n4Class, target, preLinkingPhase, idx); + } else if (n4Class instanceof N4EnumDeclaration) { + return relinkType((N4EnumDeclaration) n4Class, target, preLinkingPhase, idx); + } else if (n4Class instanceof N4TypeAliasDeclaration) { + return relinkType((N4TypeAliasDeclaration) n4Class, target, preLinkingPhase, idx); + } else if (n4Class instanceof ObjectLiteral) { + return relinkType((ObjectLiteral) n4Class, target, preLinkingPhase, idx); + } else if (n4Class instanceof NamespaceExportSpecifier) { + return relinkType((NamespaceExportSpecifier) n4Class, target, preLinkingPhase, idx); + } else if (n4Class instanceof NamespaceImportSpecifier) { + return relinkType((NamespaceImportSpecifier) n4Class, target, preLinkingPhase, idx); + } else if (n4Class instanceof TryStatement) { + return relinkType((TryStatement) n4Class, target, preLinkingPhase, idx); + } else if (n4Class instanceof TypeDefiningElement) { + return relinkType((TypeDefiningElement) n4Class, target, preLinkingPhase, idx); + } else if (n4Class instanceof VariableDeclarationContainer) { + return relinkType((VariableDeclarationContainer) n4Class, target, preLinkingPhase, idx); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(n4Class, target, preLinkingPhase, idx).toString()); + } + } + + protected int relinkFunction(final EObject n4FunctionDecl, final AbstractNamespace target, + final boolean preLinkingPhase, final int idx) { + if (n4FunctionDecl instanceof FunctionDeclaration) { + return relinkFunction((FunctionDeclaration) n4FunctionDecl, target, preLinkingPhase, idx); + } else if (n4FunctionDecl instanceof FunctionExpression) { + return relinkFunction((FunctionExpression) n4FunctionDecl, target, preLinkingPhase, idx); + } else if (n4FunctionDecl instanceof MethodDeclaration) { + return relinkFunction((MethodDeclaration) n4FunctionDecl, target, preLinkingPhase, idx); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(n4FunctionDecl, target, preLinkingPhase, idx).toString()); + } + } + + protected void createFunction(final EObject n4FunctionDecl, final AbstractNamespace target, + final boolean preLinkingPhase) { + if (n4FunctionDecl instanceof FunctionDeclaration) { + createFunction((FunctionDeclaration) n4FunctionDecl, target, preLinkingPhase); + return; + } else if (n4FunctionDecl instanceof FunctionExpression) { + createFunction((FunctionExpression) n4FunctionDecl, target, preLinkingPhase); + return; + } else if (n4FunctionDecl instanceof MethodDeclaration) { + createFunction((MethodDeclaration) n4FunctionDecl, target, preLinkingPhase); + return; + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(n4FunctionDecl, target, preLinkingPhase).toString()); + } + } + + protected void createType(final EObject n4Class, final AbstractNamespace target, final boolean preLinkingPhase) { + if (n4Class instanceof N4ClassDeclaration) { + createType((N4ClassDeclaration) n4Class, target, preLinkingPhase); + return; + } else if (n4Class instanceof N4ClassExpression) { + createType((N4ClassExpression) n4Class, target, preLinkingPhase); + return; + } else if (n4Class instanceof N4InterfaceDeclaration) { + createType((N4InterfaceDeclaration) n4Class, target, preLinkingPhase); + return; + } else if (n4Class instanceof N4EnumDeclaration) { + createType((N4EnumDeclaration) n4Class, target, preLinkingPhase); + return; + } else if (n4Class instanceof N4TypeAliasDeclaration) { + createType((N4TypeAliasDeclaration) n4Class, target, preLinkingPhase); + return; + } else if (n4Class instanceof ObjectLiteral) { + createType((ObjectLiteral) n4Class, target, preLinkingPhase); + return; + } else if (n4Class instanceof ExportDeclaration) { + createType((ExportDeclaration) n4Class, target, preLinkingPhase); + return; + } else if (n4Class instanceof NamespaceExportSpecifier) { + createType((NamespaceExportSpecifier) n4Class, target, preLinkingPhase); + return; + } else if (n4Class instanceof NamespaceImportSpecifier) { + createType((NamespaceImportSpecifier) n4Class, target, preLinkingPhase); + return; + } else if (n4Class instanceof TryStatement) { + createType((TryStatement) n4Class, target, preLinkingPhase); + return; + } else if (n4Class instanceof TypeDefiningElement) { + createType((TypeDefiningElement) n4Class, target, preLinkingPhase); + return; + } else if (n4Class instanceof VariableDeclarationContainer) { + createType((VariableDeclarationContainer) n4Class, target, preLinkingPhase); + return; + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(n4Class, target, preLinkingPhase).toString()); + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesBuilder.xtend deleted file mode 100644 index 96b50b8672..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesBuilder.xtend +++ /dev/null @@ -1,519 +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.typesbuilder - -import com.google.inject.Inject -import java.util.ArrayList -import java.util.List -import org.eclipse.emf.common.util.URI -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.n4JS.ExportDeclaration -import org.eclipse.n4js.n4JS.ExportableElement -import org.eclipse.n4js.n4JS.FunctionDeclaration -import org.eclipse.n4js.n4JS.FunctionDefinition -import org.eclipse.n4js.n4JS.FunctionExpression -import org.eclipse.n4js.n4JS.MethodDeclaration -import org.eclipse.n4js.n4JS.ModifiableElement -import org.eclipse.n4js.n4JS.N4ClassDeclaration -import org.eclipse.n4js.n4JS.N4ClassExpression -import org.eclipse.n4js.n4JS.N4EnumDeclaration -import org.eclipse.n4js.n4JS.N4InterfaceDeclaration -import org.eclipse.n4js.n4JS.N4JSASTUtils -import org.eclipse.n4js.n4JS.N4Modifier -import org.eclipse.n4js.n4JS.N4NamespaceDeclaration -import org.eclipse.n4js.n4JS.N4TypeAliasDeclaration -import org.eclipse.n4js.n4JS.NamespaceExportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.n4JS.ObjectLiteral -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.n4JS.TryStatement -import org.eclipse.n4js.n4JS.TypeDefiningElement -import org.eclipse.n4js.n4JS.VariableDeclarationContainer -import org.eclipse.n4js.naming.ModuleNameComputer -import org.eclipse.n4js.naming.SpecifierConverter -import org.eclipse.n4js.resource.N4JSResource -import org.eclipse.n4js.smith.N4JSDataCollectors -import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.StructuralTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.TClass -import org.eclipse.n4js.ts.types.TInterface -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.ts.types.TVariable -import org.eclipse.n4js.ts.types.Type -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.utils.ResourceType -import org.eclipse.n4js.validation.JavaScriptVariantHelper -import org.eclipse.n4js.workspace.WorkspaceAccess -import org.eclipse.xtext.naming.IQualifiedNameConverter -import org.eclipse.xtext.resource.DerivedStateAwareResource - -import static extension org.eclipse.n4js.utils.N4JSLanguageUtils.* - -/** - * This class with its {@link N4JSTypesBuilder#createTModuleFromSource(DerivedStateAwareResource,boolean) createTModuleFromSource()} - * method is the single entry point for the types builder package. The only exception is the public method - * {@link N4JSFunctionDefinitionTypesBuilder#updateTFunction(FunctionExpression, FunctionTypeExprOrRef, TypeRef) updateTFunction()} - * in N4JSFunctionDefinitionTypesBuilder, which is called from Xsemantics. - *

- * Derives the types model from the AST, i.e. creates a {@link TModule} with its contents - * from a {@link Script} and its children. The types model is stored at the second index of the resource. - * The types model contains for each exportable or type defining element a corresponding entry e.g. - * for a {@link N4ClassDeclaration} a {@link TClass} is created. Later when linking only the types are - * used in place of the original objects. - *

- * The types builder must not use type inference because this would lead to a resolution of lazy - * cross-references at a too early stage. Instead, the types builder creates ComputedTypeRefs that - * will later be resolved either on demand or by calling {@link N4JSResource#flattenModule()}. - */ -public class N4JSTypesBuilder { - - @Inject(optional=true) TypesFactory typesFactory = TypesFactory.eINSTANCE - @Inject extension N4JSTypesBuilderHelper - @Inject extension N4JSNamespaceDeclarationTypesBuilder - @Inject extension N4JSClassDeclarationTypesBuilder - @Inject extension N4JSInterfaceDeclarationTypesBuilder - @Inject extension N4JSEnumDeclarationTypesBuilder - @Inject extension N4JSTypeAliasDeclarationTypesBuilder - @Inject extension N4JSObjectLiteralTypesBuilder - @Inject extension N4JSFunctionDefinitionTypesBuilder - @Inject extension N4JSVariableStatementTypesBuilder - @Inject extension N4JSTypesFromTypeRefBuilder - @Inject extension N4JSImportTypesBuilder - @Inject extension N4JSExportDefinitionTypesBuilder - - @Inject extension ModuleNameComputer - @Inject private WorkspaceAccess workspaceAccess - @Inject private IQualifiedNameConverter qualifiedNameConverter - @Inject private SpecifierConverter specifierConverter - @Inject protected JavaScriptVariantHelper jsVariantHelper; - - /** Thrown when reconciliation of a TModule fails due to a hash mismatch. */ - public static class RelinkTModuleHashMismatchException extends IllegalStateException { - - public new(URI resourceURI) { - super("cannot link existing TModule to new AST due to hash mismatch: " + resourceURI); - } - } - - /** - * When demand-loading an AST for a resource that already has a TModule (usually retrieved from the index) by - * calling SyntaxRelatedTElement#getAstElement(), we are facing a challenge: we could simply replace the original - * TModule by a new TModule created in the same way as in the standard case of loading an empty resource from - * source, i.e. with method {@link N4JSTypesBuilder#createTModuleFromSource(DerivedStateAwareResource, boolean) - * #createTModuleFromSource()}. However, this would lead to the issue of all existing references to the original, - * now replaced TModule to now have an invalid target object (not contained in a resource anymore, proxy resolution - * impossible, etc.). - *

- * As a solution, this method provides a 2nd mode of the types builder in which not a new TModule is created from - * the AST, but an existing TModule is reused, i.e. the types builder does not create anything but simply creates - * the bidirectional links between AST nodes and TModule elements. - *

- * This method should be called after the AST has been loaded, with the original TModule at second position in the - * resource's contents. If the AST and TModule were created from different versions of the source, checked via an - * MD5 hash, or the rewiring fails for other reasons, an {@link IllegalStateException} is thrown. In that case, the - * state of the AST and TModule are undefined (i.e. linking may have taken place partially). - */ - def public void relinkTModuleToSource(DerivedStateAwareResource resource, boolean preLinkingPhase) { - try (val m1 = N4JSDataCollectors.dcTypesBuilder.getMeasurement(); - val m2 = N4JSDataCollectors.dcTypesBuilderRelink.getMeasurement()) { - m1.getClass(); // suppress unused variable warning from Xtend - m2.getClass(); // suppress unused variable warning from Xtend - - doRelinkTModuleToSource(resource, preLinkingPhase); - } - } - - def private void doRelinkTModuleToSource(DerivedStateAwareResource resource, boolean preLinkingPhase) { - val parseResult = resource.getParseResult(); - if (parseResult !== null) { - - val script = parseResult.rootASTElement as Script; - - val TModule module = resource.contents.get(1) as TModule; - - val astMD5New = N4JSASTUtils.md5Hex(resource); - if (astMD5New != module.astMD5) { - throw new RelinkTModuleHashMismatchException(resource.getURI()); - } - module.reconciled = true; - - script.relinkTypeModelElementsForImports(module, preLinkingPhase) - - script.buildTypesFromTypeRefs(module, preLinkingPhase); - - script.relinkTypes(module, preLinkingPhase, new RelinkIndices()); - - module.astElement = script; - script.module = module; - - } else { - throw new IllegalStateException("resource has no parse result: " + resource.URI); - } - } - - /** - * This method is the single entry point for the types builder package. The only exception is the public method - * {@link N4JSFunctionDefinitionTypesBuilder#updateTFunction(FunctionExpression,FunctionTypeExprOrRef,TypeRef) updateTFunction()} - * in N4JSFunctionDefinitionTypesBuilder, which is called from Xsemantics. - *

- * Creates an {@link TModule} with the module path name (@see {@link ModuleNameComputer#getModulePath(Resource)}) - * of the resource (replacing the dots with slashes). For all {@link ExportableElement} and - * {@link TypeDefiningElement} corresponding types like {@link TClass}, {@link TInterface} are created and assigned - * as containment references to {@link TModule}. Afterwards the so created {@link TModule} tree is browsed for - * {@link TVariable}s which do not have a type reference yet. For these there right hand side expressions is - * checked for a {@link TypeDefiningElement} for this a {@link ParameterizedTypeRef} is created having this element - * as declared type. The parameterized type ref is then assigned as type ref to the {@link TVariable}. - */ - def public void createTModuleFromSource(DerivedStateAwareResource resource, boolean preLinkingPhase) { - try (val m1 = N4JSDataCollectors.dcTypesBuilder.getMeasurement(); - val m2 = N4JSDataCollectors.dcTypesBuilderCreate.getMeasurement()) { - m1.getClass(); // suppress unused variable warning from Xtend - m2.getClass(); // suppress unused variable warning from Xtend - - doCreateTModuleFromSource(resource, preLinkingPhase); - } - } - - def private void doCreateTModuleFromSource(DerivedStateAwareResource resource, boolean preLinkingPhase) { - val parseResult = resource.getParseResult(); - if (parseResult !== null) { - -// UtilN4.takeSnapshotInGraphView("TB start (preLinking=="+preLinkingPhase+")",resource.resourceSet); - val script = parseResult.rootASTElement as Script; - - val TModule result = typesFactory.createTModule; - - result.astMD5 = N4JSASTUtils.md5Hex(resource); - result.reconciled = false; - - val qualifiedModuleName = resource.qualifiedModuleName; - result.simpleName = qualifiedModuleName.lastSegment; - result.qualifiedName = qualifiedNameConverter.toString(qualifiedModuleName); - result.preLinkingPhase = preLinkingPhase; - - val project = workspaceAccess.findProjectContaining(resource); - if (project !== null) { - result.projectID = project.name; - result.packageName = project.packageName; - result.vendorID = project.vendorId; - - // main module - val mainModuleSpec = project.mainModule; - if (mainModuleSpec !== null) { - result.mainModule = qualifiedModuleName == - specifierConverter.toQualifiedName(mainModuleSpec); - } - } - - result.copyAnnotations(script, preLinkingPhase); - - script.createTypeModelElementsForImports(result, preLinkingPhase); - - result.n4jsdModule = jsVariantHelper.isExternalMode(script); - - // Setting Polyfill property. - result.staticPolyfillModule = result.isContainedInStaticPolyfillModule; - result.staticPolyfillAware = result.isContainedInStaticPolyfillAware; - - script.buildTypesFromTypeRefs(result, preLinkingPhase); - - script.buildTypes(result, preLinkingPhase); - - result.astElement = script; - script.module = result; - (resource as N4JSResource).sneakyAddToContent(result); -// UtilN4.takeSnapshotInGraphView("TB end (preLinking=="+preLinkingPhase+")",resource.resourceSet); - } else { - throw new IllegalStateException(resource.URI + " has no parse result."); - } - } - - /** - * Create types for those TypeRefs that define a type if they play the role of an AST node. - *

- * This has to be done up-front, because in the rest of the types builder code we do not know where such a TypeRef - * shows up; to avoid having to check for them at every occurrence of a TypeRef, we do this ahead of the main types - * builder phase. - */ - def private void buildTypesFromTypeRefs(Script script, TModule target, boolean preLinkingPhase) { - if (!preLinkingPhase) { - // important to do the following in bottom-up order! - // The structural members of a higher-level StructuralTypeRef STR may contain another StructuralTypeRef STR'; - // when the TStructuralType of STR is created, the structural members are copied, including contained TypeRefs; - // at that time the lower-level STR' must already have been prepared for copying! - // Example: - // var ~Object with { - // ~Object with { number fieldOfField; } field; - // } x; - val List addTypesToTargets = new ArrayList(); - for (tr : script.eAllContents.filter(TypeRef).toList.reverseView) { - switch tr { - StructuralTypeRef: { - tr.createStructuralType() - if (tr.structuralType !== null) { - addTypesToTargets += tr.structuralType; - } - } - FunctionTypeExpression: { - tr.createTFunction() - if (tr.declaredType !== null) { - addTypesToTargets += tr.declaredType; - } - } - N4NamespaceDeclaration: { - for (Type type : addTypesToTargets) { - target.internalTypes += type; - } - addTypesToTargets.clear; - } - } - } - - for (Type type : addTypesToTargets) { - target.internalTypes += type; - } - } - } - - static class RelinkIndices { - package var namespacesIdx = 0; - package var typesIdx = 0; - package var functionIdx = 0; - package var variableIdx = 0; - } - - def private void relinkTypes(EObject container, AbstractNamespace target, boolean preLinkingPhase, RelinkIndices rlis) { - for (n : container.eContents) { - // relink types of n (if applicable) - switch n { - N4NamespaceDeclaration: - rlis.namespacesIdx = n.relinkNamespace(target, preLinkingPhase, rlis.namespacesIdx) - FunctionDefinition: - rlis.functionIdx = n.relinkFunction(target, preLinkingPhase, rlis.functionIdx) - TypeDefiningElement: - rlis.typesIdx = n.relinkType(target, preLinkingPhase, rlis.typesIdx) - VariableDeclarationContainer: - rlis.variableIdx = n.relinkType(target, preLinkingPhase, rlis.variableIdx) - TryStatement: - rlis.variableIdx = n.relinkType(target, preLinkingPhase, rlis.variableIdx) - } - // relink types of child nodes - var AbstractNamespace nextTarget; - var RelinkIndices nextRelinkIndices; - if (n instanceof N4NamespaceDeclaration) { - nextTarget = n.definedNamespace; - nextRelinkIndices = new RelinkIndices(); - } else { - nextTarget = target; - nextRelinkIndices = rlis; - } - if (nextTarget !== null) { // can be null in broken ASTs - relinkTypes(n, nextTarget, preLinkingPhase, nextRelinkIndices) - } - // handle exports - // -> nothing to do - // (AST nodes of type ExportDeclaration are not TypeDefiningElements and - // type model elements of type ExportDefinition are not SyntaxRelatedTElements, - // so we do not have to relink anything between AST and types model here) - } - } - - def protected int relinkNamespace(N4NamespaceDeclaration n4Namespace, AbstractNamespace target, boolean preLinkingPhase, int idx) { - if (n4Namespace.relinkTNamespace(target, preLinkingPhase, idx)) { - return idx + 1; - } - return idx; - } - - def protected dispatch int relinkType(TypeDefiningElement other, AbstractNamespace target, boolean preLinkingPhase, int idx) { - throw new IllegalArgumentException("unknown subclass of TypeDefiningElement: " + other?.eClass.name); - } - - def protected dispatch int relinkType(NamespaceImportSpecifier nsImpSpec, AbstractNamespace target, boolean preLinkingPhase, int idx) { - // already handled up-front in N4JSNamespaceImportTypesBuilder#relinkNamespaceTypes - return idx; - } - - def protected dispatch int relinkFunction(MethodDeclaration n4MethodDecl, AbstractNamespace target, boolean preLinkingPhase, int idx) { - // methods are handled in their containing class/interface -> ignore them here - return idx; - } - - def protected dispatch int relinkFunction(FunctionDeclaration n4FunctionDecl, AbstractNamespace target, boolean preLinkingPhase, int idx) { - if (n4FunctionDecl.relinkTFunction(target, preLinkingPhase, idx)) { - return idx + 1; - } - return idx; - } - - /** Function expressions are special, see {@link N4JSFunctionDefinitionTypesBuilder#createTFunction(FunctionExpression,TModule,boolean)}. */ - def protected dispatch int relinkFunction(FunctionExpression n4FunctionExpr, AbstractNamespace target, boolean preLinkingPhase, int idx) { - n4FunctionExpr.createTFunction(target, preLinkingPhase); - return idx; - } - - def protected dispatch int relinkType(N4ClassDeclaration n4Class, AbstractNamespace target, boolean preLinkingPhase, int idx) { - if (n4Class.relinkTClass(target, preLinkingPhase, idx)) { - return idx + 1; - } - return idx; - } - - def protected dispatch int relinkType(N4ClassExpression n4Class, AbstractNamespace target, boolean preLinkingPhase, int idx) { - n4Class.createTClass(target, preLinkingPhase) - // do not increment the index - return idx - } - - def protected dispatch int relinkType(N4InterfaceDeclaration n4Interface, AbstractNamespace target, boolean preLinkingPhase, int idx) { - if (n4Interface.relinkTInterface(target, preLinkingPhase, idx)) { - return idx + 1; - } - return idx; - } - - def protected dispatch int relinkType(N4EnumDeclaration n4Enum, AbstractNamespace target, boolean preLinkingPhase, int idx) { - if (n4Enum.relinkTEnum(target, preLinkingPhase, idx)) { - return idx + 1; - } - return idx; - } - - def protected dispatch int relinkType(N4TypeAliasDeclaration n4TypeAlias, AbstractNamespace target, boolean preLinkingPhase, int idx) { - if (n4TypeAlias.relinkTypeAlias(target, preLinkingPhase, idx)) { - return idx + 1; - } - return idx; - } - - def protected dispatch int relinkType(ObjectLiteral objectLiteral, AbstractNamespace target, boolean preLinkingPhase, int idx) { - objectLiteral.createObjectLiteral(target, preLinkingPhase) - return idx; - } - - def protected dispatch int relinkType(VariableDeclarationContainer n4VarDeclContainer, AbstractNamespace target, boolean preLinkingPhase, int idx) { - return n4VarDeclContainer.relinkVariableTypes(target, preLinkingPhase, idx) - } - - def protected dispatch int relinkType(TryStatement tryStmnt, AbstractNamespace target, boolean preLinkingPhase, int idx) { - return tryStmnt.relinkVariableTypes(target, preLinkingPhase, idx) - } - - def protected dispatch int relinkType(NamespaceExportSpecifier n4NamespaceExportSpecifier, AbstractNamespace target, boolean preLinkingPhase, int idx) { - // namespace export specifiers are handled as part of their containing ExportDeclaration -> ignore them here - return idx; - } - - - def private void buildTypes(EObject container, AbstractNamespace target, boolean preLinkingPhase) { - for (n : container.eContents) { - // build type for n (if applicable) - switch n { - N4NamespaceDeclaration: - n.createNamespace(target, preLinkingPhase) - FunctionDefinition: - n.createFunction(target, preLinkingPhase) - TypeDefiningElement: - n.createType(target, preLinkingPhase) - VariableDeclarationContainer: // VariableStatement and ForStatement - n.createType(target, preLinkingPhase) - TryStatement: - n.createType(target, preLinkingPhase) - } - // build types for child nodes - val nextTarget = if (n instanceof N4NamespaceDeclaration) n.definedNamespace else target; - if (nextTarget !== null) { // can be null in broken ASTs - buildTypes(n, nextTarget, preLinkingPhase); - } - // handle exports - if (n instanceof ExportDeclaration) { - n.createType(target, preLinkingPhase); - } else if (n instanceof ExportableElement) { - if (!n.isDeclaredExported && n.isExportedByNamespace) { - val isDtsExceptionCase = ResourceType.getResourceType(n) === ResourceType.DTS - && n instanceof ModifiableElement - && (n as ModifiableElement).declaredModifiers.contains(N4Modifier.PRIVATE); - if (!isDtsExceptionCase) { - n.createExportDefinitionForDirectlyExportedElement(target, preLinkingPhase); - } - } - } - } - } - - def protected void createNamespace(N4NamespaceDeclaration n4Namespace, AbstractNamespace target, boolean preLinkingPhase) { - n4Namespace.createTNamespace(target, preLinkingPhase) - } - - def protected dispatch void createFunction(MethodDeclaration n4MethodDecl, AbstractNamespace target, boolean preLinkingPhase) { - // methods are handled in their containing class/interface -> ignore them here - } - - def protected dispatch void createFunction(FunctionDeclaration n4FunctionDecl, AbstractNamespace target, boolean preLinkingPhase) { - n4FunctionDecl.createTFunction(target, preLinkingPhase) - } - - /** Function expressions are special, see {@link N4JSFunctionDefinitionTypesBuilder#createTFunction(FunctionExpression,TModule,boolean)}. */ - def protected dispatch void createFunction(FunctionExpression n4FunctionExpr, AbstractNamespace target, boolean preLinkingPhase) { - n4FunctionExpr.createTFunction(target, preLinkingPhase) - } - - def protected dispatch void createType(TypeDefiningElement other, AbstractNamespace target, boolean preLinkingPhase) { - throw new IllegalArgumentException("unknown subclass of TypeDefiningElement: " + other?.eClass.name); - } - - def protected dispatch void createType(NamespaceImportSpecifier nsImpSpec, AbstractNamespace target, boolean preLinkingPhase) { - // already handled up-front in #buildNamespacesTypesFromModuleImports() - } - - def protected dispatch void createType(N4ClassDeclaration n4Class, AbstractNamespace target, boolean preLinkingPhase) { - n4Class.createTClass(target, preLinkingPhase) - } - - def protected dispatch void createType(N4ClassExpression n4Class, AbstractNamespace target, boolean preLinkingPhase) { - n4Class.createTClass(target, preLinkingPhase) - } - - def protected dispatch void createType(N4InterfaceDeclaration n4Interface, AbstractNamespace target, boolean preLinkingPhase) { - n4Interface.createTInterface(target, preLinkingPhase) - } - - def protected dispatch void createType(N4EnumDeclaration n4Enum, AbstractNamespace target, boolean preLinkingPhase) { - n4Enum.createTEnum(target, preLinkingPhase) - } - - def protected dispatch void createType(N4TypeAliasDeclaration n4TypeAliasDecl, AbstractNamespace target, boolean preLinkingPhase) { - n4TypeAliasDecl.createTypeAlias(target, preLinkingPhase) - } - - def protected dispatch void createType(ObjectLiteral objectLiteral, AbstractNamespace target, boolean preLinkingPhase) { - objectLiteral.createObjectLiteral(target, preLinkingPhase) - } - - def protected dispatch void createType(VariableDeclarationContainer n4VarDeclContainer, AbstractNamespace target, boolean preLinkingPhase) { - n4VarDeclContainer.createVariableTypes(target, preLinkingPhase) - } - - def protected dispatch void createType(TryStatement tryStmnt, AbstractNamespace target, boolean preLinkingPhase) { - tryStmnt.createVariableTypes(target, preLinkingPhase) - } - - def protected dispatch void createType(ExportDeclaration n4ExportDeclaration, AbstractNamespace target, boolean preLinkingPhase) { - n4ExportDeclaration.createExportDefinition(target, preLinkingPhase) - } - - def protected dispatch void createType(NamespaceExportSpecifier n4NamespaceExportSpecifier, AbstractNamespace target, boolean preLinkingPhase) { - // namespace export specifiers are handled as part of their containing ExportDeclaration -> ignore them here - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesBuilderHelper.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesBuilderHelper.java new file mode 100644 index 0000000000..32fa2c6da7 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesBuilderHelper.java @@ -0,0 +1,306 @@ +/** + * 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.typesbuilder; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filterNull; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.head; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.isNullOrEmpty; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.log4j.Logger; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.compileTime.CompileTimeEvaluator; +import org.eclipse.n4js.compileTime.CompileTimeValue; +import org.eclipse.n4js.n4JS.AnnotableElement; +import org.eclipse.n4js.n4JS.Annotation; +import org.eclipse.n4js.n4JS.AnnotationArgument; +import org.eclipse.n4js.n4JS.FunctionDefinition; +import org.eclipse.n4js.n4JS.LiteralAnnotationArgument; +import org.eclipse.n4js.n4JS.LiteralOrComputedPropertyName; +import org.eclipse.n4js.n4JS.ModifiableElement; +import org.eclipse.n4js.n4JS.ModifierUtils; +import org.eclipse.n4js.n4JS.N4ClassifierDeclaration; +import org.eclipse.n4js.n4JS.N4MemberDeclaration; +import org.eclipse.n4js.n4JS.N4Modifier; +import org.eclipse.n4js.n4JS.NamedElement; +import org.eclipse.n4js.n4JS.PropertyNameKind; +import org.eclipse.n4js.n4JS.PropertyNameOwner; +import org.eclipse.n4js.n4JS.TypeDefiningElement; +import org.eclipse.n4js.n4JS.TypeRefAnnotationArgument; +import org.eclipse.n4js.postprocessing.ComputedNameProcessor; +import org.eclipse.n4js.scoping.N4JSScopeProviderLocalOnly; +import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.AccessibleTypeElement; +import org.eclipse.n4js.ts.types.FieldAccessor; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.MemberAccessModifier; +import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType; +import org.eclipse.n4js.ts.types.TAnnotableElement; +import org.eclipse.n4js.ts.types.TAnnotation; +import org.eclipse.n4js.ts.types.TAnnotationStringArgument; +import org.eclipse.n4js.ts.types.TAnnotationTypeRefArgument; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.ts.types.TypeAccessModifier; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.typesystem.utils.RuleEnvironment; +import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.TameAutoClosable; +import org.eclipse.n4js.validation.JavaScriptVariantHelper; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +@Singleton +class N4JSTypesBuilderHelper { + + @Inject + private JavaScriptVariantHelper jsVariantHelper; + @Inject + private CompileTimeEvaluator compileTimeEvaluator; + @Inject + private N4JSScopeProviderLocalOnly n4jsScopeProviderLocalOnly; + + private static Logger logger = Logger.getLogger(N4JSTypesBuilderHelper.class); + + protected void setTypeAccessModifier( + AccessibleTypeElement classifier, T definition) { + + boolean isPlainJS = jsVariantHelper.isPlainJS(definition); + // IDEBUG-861 assume public visibility if plain JS + if (isPlainJS) { + classifier.setDeclaredTypeAccessModifier(TypeAccessModifier.PUBLIC); + } else { + classifier.setDeclaredTypeAccessModifier(ModifierUtils.convertToTypeAccessModifier( + definition.getDeclaredModifiers(), definition.getAllAnnotations())); + } + } + + void setProvidedByRuntime(AccessibleTypeElement declaredType, AnnotableElement annotableElement, + @SuppressWarnings("unused") boolean preLinkingPhase) { + + declaredType.setDeclaredProvidedByRuntime(AnnotationDefinition.PROVIDED_BY_RUNTIME.hasAnnotation( + annotableElement)); + } + + /** + * Adds references to a feature (via the closure), but copies the references in order to avoid problems with + * containment relations. The references are copied with proxies (see {@link TypeUtils#copyWithProxies(EObject)} in + * order to avoid resolving of proxies here. Null values (usually caused by a syntax error) are omitted, i.e. + * indices are not preserved. + */ + void addCopyOfReferences(List target, List values) { + if (isNullOrEmpty(values)) { + return; + } + if (exists(values, it -> it != null && it.eIsProxy())) { + throw new IllegalStateException("There is a proxy in the list, cannot copy and set references"); + } + target.addAll(toList(map(filterNull(values), it -> TypeUtils.copyWithProxies(it)))); + } + + /** + * Initializes the name of a TMember in the TModule based the member/property declaration in the AST. In case of + * computed property names, this method will keep the name in the TModule set to null and + * {@link ComputedNameProcessor} will later change it to the actual name during post-processing. + */ + void setMemberName(TMember tMember, PropertyNameOwner n4MemberOrPropertyAssignment) { + // this will be 'null' in case of a computed property name + tMember.setName(n4MemberOrPropertyAssignment.getName()); + LiteralOrComputedPropertyName declaredName = n4MemberOrPropertyAssignment.getDeclaredName(); + tMember.setHasComputedName(declaredName != null && declaredName.getKind() == PropertyNameKind.COMPUTED); + } + + /** + * Translates AST related member access modifier (and annotation {@code @Interanl}) to type model related member + * access modifier. + */ + void setMemberAccessModifier(Consumer memberAccessModifierAssignment, + Collection modifiers, List annotations) { + memberAccessModifierAssignment.accept(ModifierUtils.convertToMemberAccessModifier(modifiers, annotations)); + } + + /** Returns newly created reference to built-in type any. */ + ParameterizedTypeRef createAnyTypeRef(EObject object) { + ResourceSet rs = null; + if (object != null && object.eResource() != null) { + rs = object.eResource().getResourceSet(); + } + if (rs != null) { + return BuiltInTypeScope.get(rs).getAnyTypeRef(); + } + return null; + } + + /** + * Copies annotations from AST to Type model. Note that not all annotations are copied, only the ones listed in + * {@link AnnotationDefinition#ANNOTATIONS_IN_TYPEMODEL}. Also annotations contained in containing export + * declaration are copied, as this construct is not present in type model and its annotations are to be defined at + * the type. + *

+ * Since this mechanism is changed anyway, no type check (whether annotation is allowed for given element type) is + * performed. + */ + void copyAnnotations(TAnnotableElement typeTarget, AnnotableElement ast, + @SuppressWarnings("unused") boolean preLinkingPhase) { + + for (Annotation ann : ast.getAllAnnotations()) { + if (AnnotationDefinition.isInTypeModel(ann.getName())) { + TAnnotation ta = TypesFactory.eINSTANCE.createTAnnotation(); + ta.setName(ann.getName()); + + for (AnnotationArgument arg : ann.getArgs()) { + if (arg instanceof LiteralAnnotationArgument) { + TAnnotationStringArgument targ = TypesFactory.eINSTANCE.createTAnnotationStringArgument(); + targ.setValue(((LiteralAnnotationArgument) arg).getLiteral().getValueAsString()); + ta.getArgs().add(targ); + } else if (arg instanceof TypeRefAnnotationArgument) { + TAnnotationTypeRefArgument targ = TypesFactory.eINSTANCE.createTAnnotationTypeRefArgument(); + TypeRefAnnotationArgument traa = (TypeRefAnnotationArgument) arg; + targ.setTypeRef(TypeUtils.copyWithProxies( + traa.getTypeRefNode() == null ? null : traa.getTypeRefNode().getTypeRefInAST())); + ta.getArgs().add(targ); + } + } + typeTarget.getAnnotations().add(ta); + } + } + } + + /** Returns newly created reference to built-in type any. */ + TClassifier getObjectType(EObject object) { + ResourceSet rs = null; + if (object != null && object.eResource() != null) { + rs = object.eResource().getResourceSet(); + } + if (rs != null) { + return BuiltInTypeScope.get(rs).getObjectType(); + } + return null; + } + + /** @see TClassifier#isDeclaredCovariantConstructor() */ + boolean isDeclaredCovariantConstructor(N4ClassifierDeclaration classifierDecl) { + if (AnnotationDefinition.COVARIANT_CONSTRUCTOR.hasAnnotation(classifierDecl)) { + return true; + } + N4MemberDeclaration ctor = findFirst(classifierDecl.getOwnedMembers(), m -> m.isConstructor()); + return ctor != null && AnnotationDefinition.COVARIANT_CONSTRUCTOR.hasAnnotation(ctor); + } + + /** Handle optional "@This" annotation and set its argument as declared this type in types model element. */ + protected void setDeclaredThisTypeFromAnnotation(TFunction functionType, FunctionDefinition functionDef, + boolean preLinkingPhase) { + if (!preLinkingPhase) { + functionType.setDeclaredThisType(TypeUtils.copyWithProxies( + internalGetDeclaredThisTypeFromAnnotation(functionDef))); + } + } + + /** Handle optional "@This" annotation and set its argument as declared this type in types model element. */ + protected void setDeclaredThisTypeFromAnnotation(FieldAccessor accessorType, + org.eclipse.n4js.n4JS.FieldAccessor accessorDecl, boolean preLinkingPhase) { + if (!preLinkingPhase) { + accessorType.setDeclaredThisType(TypeUtils.copyWithProxies( + internalGetDeclaredThisTypeFromAnnotation(accessorDecl))); + } + } + + private TypeRef internalGetDeclaredThisTypeFromAnnotation(AnnotableElement element) { + Annotation annThis = AnnotationDefinition.THIS.getAnnotation(element); + if (annThis != null && annThis.getArgs() != null) { + TypeRefAnnotationArgument traa = head(filter(annThis.getArgs(), TypeRefAnnotationArgument.class)); + if (traa != null && traa.getTypeRefNode() != null) { + return traa.getTypeRefNode().getTypeRefInAST(); + } + } + return null; + } + + /** + * Used during + * {@link N4JSTypesBuilder#relinkTModuleToSource(org.eclipse.xtext.resource.DerivedStateAwareResource, boolean) + * relinking}, to ensure consistency of named elements between newly loaded AST and original TModule. + */ + protected void ensureEqualName(NamedElement astNode, IdentifiableElement moduleElement) { + ensureEqualName(astNode, moduleElement.getName()); + } + + protected void ensureEqualName(NamedElement astNode, String nameInModule) { + String nameInAST = astNode == null ? null : astNode.getName(); + if (astNode != null && nameInAST != null) { + // note: no check if no name available in AST (don't fiddle with computed property names, etc.) + if (!nameInAST.equals(nameInModule)) { + String msg = "inconsistency between newly loaded AST and to-be-linked TModule: " + + "nameInAST=" + nameInAST + ", " + + "nameInModule=" + nameInModule + ", " + + "in: " + (astNode.eResource() == null ? null : astNode.eResource().getURI().toString()); + logger.error(msg); + throw new IllegalStateException(msg); + } + } + } + + ModuleNamespaceVirtualType addNewModuleNamespaceVirtualType(TModule target, String name, TModule wrappedModule, + boolean dynamic, TypeDefiningElement astNode) { + ModuleNamespaceVirtualType type = TypesFactory.eINSTANCE.createModuleNamespaceVirtualType(); + type.setName(name); + type.setModule(wrappedModule); + type.setDeclaredDynamic(dynamic); + + type.setAstElement(astNode); + astNode.setDefinedType(type); + + target.getInternalTypes().add(type); + + return type; + } + + boolean hasValidName(PropertyNameOwner pno) { + if (pno.getName() == null && pno.hasComputedPropertyName()) { + if (N4JSLanguageUtils.isProcessedAsCompileTimeExpression(pno.getDeclaredName().getExpression())) { + return false; + } + + LiteralOrComputedPropertyName litOrComp = pno.getDeclaredName(); + RuleEnvironment G = RuleEnvironmentExtensions.newRuleEnvironment(pno); + try (TameAutoClosable tac = n4jsScopeProviderLocalOnly.newCrossFileResolutionSuppressor()) { + CompileTimeValue value = compileTimeEvaluator.evaluateCompileTimeExpression(G, + litOrComp.getExpression()); + String name = N4JSLanguageUtils.derivePropertyNameFromCompileTimeValue(value); + if (name == null) { + return false; + } + } + } + return true; + } + + boolean validPNO(PropertyNameOwner pno) { + return pno.getName() != null || pno.hasComputedPropertyName(); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesBuilderHelper.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesBuilderHelper.xtend deleted file mode 100644 index c2f0a15e92..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesBuilderHelper.xtend +++ /dev/null @@ -1,265 +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.typesbuilder - -import com.google.inject.Inject -import com.google.inject.Singleton -import java.util.Collection -import java.util.List -import org.apache.log4j.Logger -import org.eclipse.emf.ecore.EObject -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.compileTime.CompileTimeEvaluator -import org.eclipse.n4js.n4JS.AnnotableElement -import org.eclipse.n4js.n4JS.Annotation -import org.eclipse.n4js.n4JS.FunctionDefinition -import org.eclipse.n4js.n4JS.LiteralAnnotationArgument -import org.eclipse.n4js.n4JS.ModifiableElement -import org.eclipse.n4js.n4JS.ModifierUtils -import org.eclipse.n4js.n4JS.N4ClassifierDeclaration -import org.eclipse.n4js.n4JS.N4Modifier -import org.eclipse.n4js.n4JS.NamedElement -import org.eclipse.n4js.n4JS.PropertyNameKind -import org.eclipse.n4js.n4JS.PropertyNameOwner -import org.eclipse.n4js.n4JS.TypeDefiningElement -import org.eclipse.n4js.n4JS.TypeRefAnnotationArgument -import org.eclipse.n4js.postprocessing.ComputedNameProcessor -import org.eclipse.n4js.scoping.N4JSScopeProviderLocalOnly -import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.AccessibleTypeElement -import org.eclipse.n4js.ts.types.FieldAccessor -import org.eclipse.n4js.ts.types.IdentifiableElement -import org.eclipse.n4js.ts.types.MemberAccessModifier -import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType -import org.eclipse.n4js.ts.types.TAnnotableElement -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TFunction -import org.eclipse.n4js.ts.types.TMember -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.ts.types.TypeAccessModifier -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.validation.JavaScriptVariantHelper - -@Singleton -package class N4JSTypesBuilderHelper { - - @Inject private JavaScriptVariantHelper jsVariantHelper; - @Inject private CompileTimeEvaluator compileTimeEvaluator; - @Inject private N4JSScopeProviderLocalOnly n4jsScopeProviderLocalOnly; - - private static Logger logger = Logger.getLogger(N4JSTypesBuilderHelper); - - - def protected void setTypeAccessModifier( - AccessibleTypeElement classifier, T definition) { - - val isPlainJS = jsVariantHelper.isPlainJS(definition); - // IDEBUG-861 assume public visibility if plain JS - if (isPlainJS) { - classifier.declaredTypeAccessModifier = TypeAccessModifier.PUBLIC; - } else { - classifier.declaredTypeAccessModifier = ModifierUtils.convertToTypeAccessModifier( - definition.declaredModifiers, definition.allAnnotations); - } - } - - def package void setProvidedByRuntime(AccessibleTypeElement declaredType, - AnnotableElement annotableElement, boolean preLinkingPhase) { - - declaredType.declaredProvidedByRuntime = AnnotationDefinition.PROVIDED_BY_RUNTIME.hasAnnotation( - annotableElement); - } - - /** - * Adds references to a feature (via the closure), but copies the references in order to avoid problems with containment relations. - * The references are copied with proxies (see {@link TypeUtils#copyWithProxies(EObject)} in order to avoid - * resolving of proxies here. Null values (usually caused by a syntax error) are omitted, i.e. indices are not preserved. - * - * @param typeListAssignment closure, actually adds the processed list - * @param listToAssign the list with references, there must be no proxy in the list - * @param preLinkingPhase if true, references are not set (they are only set in the linking phase) - * @param reference type, e.g., ParameterizedTypeRef - */ - def package void addCopyOfReferences(List target, List values) { - if (values.isNullOrEmpty) { - return - } - if (values.exists[it !== null && eIsProxy]) { - throw new IllegalStateException("There is a proxy in the list, cannot copy and set references"); - } - target += values.filterNull.map[TypeUtils.copyWithProxies(it)] - } - - /** - * Initializes the name of a TMember in the TModule based the member/property declaration in the AST. In case of - * computed property names, this method will keep the name in the TModule set to null and - * {@link ComputedNameProcessor} will later change it to the actual name during post-processing. - */ - def package void setMemberName(TMember tMember, PropertyNameOwner n4MemberOrPropertyAssignment) { - tMember.name = n4MemberOrPropertyAssignment.name; // this will be 'null' in case of a computed property name - tMember.hasComputedName = n4MemberOrPropertyAssignment.declaredName?.kind === PropertyNameKind.COMPUTED; - } - - /** - * Translates AST related member access modifier (and annotation {@code @Interanl}) to type model related member access modifier. - */ - def package void setMemberAccessModifier((MemberAccessModifier)=>void memberAccessModifierAssignment, - Collection modifiers, List annotations) { - memberAccessModifierAssignment.apply(ModifierUtils.convertToMemberAccessModifier(modifiers, annotations)); - } - - /** Returns newly created reference to built-in type any. */ - def package ParameterizedTypeRef createAnyTypeRef(EObject object) { - val rs = object?.eResource?.resourceSet - if (rs !== null) - BuiltInTypeScope.get(rs).anyTypeRef - else - null - } - - /** - * Copies annotations from AST to Type model. Note that not all annotations are copied, only the ones listed in - * {@link #ANNOTATIONS_IN_TYPE_MODEL}. Also annotations contained in containing export declaration are copied, as this - * construct is not present in type model and its annotations are to be defined at the type. - *

- * Since this mechanism is changed anyway, no type check (whether annotation is allowed for given element type) is - * performed. - */ - def package void copyAnnotations(TAnnotableElement typeTarget, AnnotableElement ast, boolean preLinkingPhase) { - typeTarget.annotations += ast.allAnnotations.filter[AnnotationDefinition.isInTypeModel(name)].map [ - val ta = TypesFactory.eINSTANCE.createTAnnotation(); - ta.name = it.name; - ta.args.addAll(args.map [ - switch (it) { - LiteralAnnotationArgument: { - val arg = TypesFactory.eINSTANCE.createTAnnotationStringArgument(); - arg.value = it.literal.valueAsString; - return arg; - } - TypeRefAnnotationArgument: { - val arg = TypesFactory.eINSTANCE.createTAnnotationTypeRefArgument(); - arg.typeRef = TypeUtils.copyWithProxies(typeRefNode?.typeRefInAST); - return arg; - } - } - ]) - return ta - ] - } - - /** Returns newly created reference to built-in type any. */ - def package TClassifier getObjectType(EObject object) { - val rs = object?.eResource?.resourceSet - if (rs !== null) - BuiltInTypeScope.get(rs).objectType - else - null - } - - /** @see TClassifier#isDeclaredCovariantConstructor() */ - def package boolean isDeclaredCovariantConstructor(N4ClassifierDeclaration classifierDecl) { - if (AnnotationDefinition.COVARIANT_CONSTRUCTOR.hasAnnotation(classifierDecl)) { - return true; - } - val ctor = classifierDecl.ownedMembers.findFirst[isConstructor]; - return ctor !== null && AnnotationDefinition.COVARIANT_CONSTRUCTOR.hasAnnotation(ctor); - } - - /** Handle optional "@This" annotation and set its argument as declared this type in types model element. */ - def protected void setDeclaredThisTypeFromAnnotation(TFunction functionType, FunctionDefinition functionDef, - boolean preLinkingPhase) { - if (!preLinkingPhase) - functionType.declaredThisType = TypeUtils.copyWithProxies( - internalGetDeclaredThisTypeFromAnnotation(functionDef)); - } - - /** Handle optional "@This" annotation and set its argument as declared this type in types model element. */ - def protected void setDeclaredThisTypeFromAnnotation(FieldAccessor accessorType, - org.eclipse.n4js.n4JS.FieldAccessor accessorDecl, boolean preLinkingPhase) { - if (!preLinkingPhase) - accessorType.declaredThisType = TypeUtils.copyWithProxies( - internalGetDeclaredThisTypeFromAnnotation(accessorDecl)); - } - - def private TypeRef internalGetDeclaredThisTypeFromAnnotation(AnnotableElement element) { - val annThis = AnnotationDefinition.THIS.getAnnotation(element); - return annThis?.args?.filter(TypeRefAnnotationArgument)?.head?.typeRefNode?.typeRefInAST; - } - - - /** - * Used during {@link N4JSTypesBuilder#relinkTModuleToSource(org.eclipse.xtext.resource.DerivedStateAwareResource, - * boolean) relinking}, to ensure consistency of named elements between newly loaded AST and original TModule. - */ - def protected void ensureEqualName(NamedElement astNode, IdentifiableElement moduleElement) { - ensureEqualName(astNode, moduleElement.name); - } - - def protected void ensureEqualName(NamedElement astNode, String nameInModule) { - val nameInAST = astNode?.name; - if (nameInAST !== null) { // note: no check if no name available in AST (don't fiddle with computed property names, etc.) - if (!nameInAST.equals(nameInModule)) { - val msg = "inconsistency between newly loaded AST and to-be-linked TModule: " - + "nameInAST=" + nameInAST + ", " - + "nameInModule=" + nameInModule + ", " - + "in: " + astNode.eResource?.URI; - logger.error(msg); - throw new IllegalStateException(msg); - } - } - } - - def package ModuleNamespaceVirtualType addNewModuleNamespaceVirtualType(TModule target, String name, TModule wrappedModule, boolean dynamic, TypeDefiningElement astNode) { - val type = TypesFactory.eINSTANCE.createModuleNamespaceVirtualType - type.name = name; - type.module = wrappedModule; - type.declaredDynamic = dynamic; - - type.astElement = astNode; - astNode.definedType = type; - - target.internalTypes += type; - - return type; - } - - def package boolean hasValidName(PropertyNameOwner pno) { - if (pno.name === null && pno.hasComputedPropertyName) { - if (N4JSLanguageUtils.isProcessedAsCompileTimeExpression(pno?.declaredName.expression)) { - return false; - } - - val litOrComp = pno.declaredName; - val G = RuleEnvironmentExtensions.newRuleEnvironment(pno); - val tac = n4jsScopeProviderLocalOnly.newCrossFileResolutionSuppressor(); - try { - val value = compileTimeEvaluator.evaluateCompileTimeExpression(G, litOrComp.expression); - val name = N4JSLanguageUtils.derivePropertyNameFromCompileTimeValue(value); - if (name === null) { - return false; - } - } finally { - tac.close(); - } - } - return true; - } - - - def package boolean validPNO(PropertyNameOwner pno) { - return pno.name !== null || pno.hasComputedPropertyName; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesFromTypeRefBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesFromTypeRefBuilder.java new file mode 100644 index 0000000000..e76c2cb91a --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesFromTypeRefBuilder.java @@ -0,0 +1,199 @@ +/** + * 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.typesbuilder; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.Arrays; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression; +import org.eclipse.n4js.ts.typeRefs.StructuralTypeRef; +import org.eclipse.n4js.ts.types.TFormalParameter; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.TStructField; +import org.eclipse.n4js.ts.types.TStructGetter; +import org.eclipse.n4js.ts.types.TStructMember; +import org.eclipse.n4js.ts.types.TStructMethod; +import org.eclipse.n4js.ts.types.TStructSetter; +import org.eclipse.n4js.ts.types.TStructuralType; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.utils.N4JSLanguageUtils; + +/** + * Methods for creating types from TypeRefs are collected in this class. + *

+ * The methods of this class might seem different from other createXYZ() methods in the types builder package, in that + * they take a subclass of TypeRef and not a typical AST node element. However, SturcturalTypeRefs and + * FunctionTypeExpressions are among those TypeRefs that may appear in the AST and play the role of an AST + * node. The methods in this class will only be invoked for such TypeRefs that appear in the AST, so these method are, + * in fact, very similar to the other createXYZ() methods. + */ +public class N4JSTypesFromTypeRefBuilder { + + /** + * Creates a TStructuralType in the target module if the StructuralTypeRef has structural members defined (in the + * with-clause). For more details why this is required, see API doc of StructuralTypeRef. + */ + void createStructuralType(StructuralTypeRef structTypeRef) { + if (structTypeRef.getAstStructuralMembers().isEmpty()) { + return; + } + + ResourceSet resSet = structTypeRef.eResource().getResourceSet(); + if (resSet == null) { + throw new IllegalArgumentException("structTypeRef must be contained in AST"); + } + + BuiltInTypeScope builtInTypeScope = BuiltInTypeScope.get(resSet); + TStructuralType structType = TypesFactory.eINSTANCE.createTStructuralType(); + + for (TStructMember memberInAST : structTypeRef.getAstStructuralMembers()) { + TStructMember memberForTModule = createTStructMember(memberInAST, builtInTypeScope); + if (memberInAST.isASTCallSignature()) { + if (structType.getCallSignature() == null) { + structType.setCallSignature((TStructMethod) memberForTModule); + } else { + // error case: duplicate call signatures + // --> to avoid scoping from returning elements that are not contained in a resource (esp. in case + // of + // type parameters of generic call signatures), we have to add 'memberForTModule' as an ordinary + // member + structType.getOwnedMembers().add(memberForTModule); + } + } else if (memberInAST.isASTConstructSignature()) { + if (structType.getConstructSignature() == null) { + structType.setConstructSignature((TStructMethod) memberForTModule); + } else { + // error case: duplicate construct signatures + // --> we have to add 'memberForTModule' as an ordinary member (see above) + structType.getOwnedMembers().add(memberForTModule); + } + } else { + structType.getOwnedMembers().add(memberForTModule); + } + } + + structTypeRef.setStructuralType(structType); + } + + private T createTStructMember(T memberInAST, BuiltInTypeScope builtInTypeScope) { + if (memberInAST == null) { + return null; + } + T memberForModule = TypeUtils.copyWithProxies(memberInAST); + applyDefaults(builtInTypeScope, memberForModule); + memberForModule.setAstElement(memberInAST); + memberInAST.setDefinedMember(memberForModule); + return memberForModule; + } + + /** + * Creates a TFunction in the target module if the FunctionTypeExpression is generic. For more details why this is + * required, see API doc of FunctionTypeExpression. + */ + void createTFunction(FunctionTypeExpression fte) { + if (!fte.isGeneric()) { + return; + } + + ResourceSet resSet = fte.eResource().getResourceSet(); + if (resSet == null) { + throw new IllegalArgumentException("fte must be contained in AST"); + } + + BuiltInTypeScope builtInTypeScope = BuiltInTypeScope.get(resSet); + TFunction ft = TypesFactory.eINSTANCE.createTFunction(); + + // TODO support hyper links + ft.getTypeVars().addAll(toList(map(fte.getTypeVars(), currTypeVar -> TypeUtils.copyWithProxies(currTypeVar)))); + ft.getFpars().addAll(toList(map(fte.getFpars(), currFpar -> { + TFormalParameter clone = TypeUtils.copyWithProxies(currFpar); + applyDefaults(builtInTypeScope, (EObject) clone); + clone.setAstElement(currFpar); + return clone; + }))); + ft.setReturnTypeRef(TypeUtils.copyWithProxies(fte.getReturnTypeRef())); + ft.setDeclaredThisType(TypeUtils.copyWithProxies(fte.getDeclaredThisType())); + + if (ft.getReturnTypeRef() == null) { + ft.setReturnTypeRef(builtInTypeScope.getAnyTypeRef()); + } + + fte.setDeclaredType(ft); + ft.setAstElement(fte); + } + + private void applyDefaults(BuiltInTypeScope builtInTypeScope, EObject getter) { + if (getter instanceof TStructGetter) { + applyDefaults(builtInTypeScope, (TStructGetter) getter); + return; + } else if (getter instanceof TStructMethod) { + applyDefaults(builtInTypeScope, (TStructMethod) getter); + return; + } else if (getter instanceof TStructSetter) { + applyDefaults(builtInTypeScope, (TStructSetter) getter); + return; + } else if (getter instanceof TStructField) { + applyDefaults(builtInTypeScope, (TStructField) getter); + return; + } else if (getter instanceof TFormalParameter) { + applyDefaults(builtInTypeScope, (TFormalParameter) getter); + return; + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(builtInTypeScope, getter).toString()); + } + } + + private void applyDefaults(BuiltInTypeScope builtInTypeScope, TStructField field) { + if (field.getTypeRef() == null) { + field.setTypeRef(builtInTypeScope.getAnyTypeRef()); + } + } + + private void applyDefaults(BuiltInTypeScope builtInTypeScope, TStructGetter getter) { + if (getter.getTypeRef() == null) { + getter.setTypeRef(builtInTypeScope.getAnyTypeRef()); + } + } + + private void applyDefaults(BuiltInTypeScope builtInTypeScope, TStructSetter setter) { + // note: setter.fpar==null and setter.fpar.getTypeRef()==null are disallowed by syntax, but + // setting default types for these cases gives us better behavior in case of broken ASTs + if (setter.getFpar() == null) { + setter.setFpar(TypesFactory.eINSTANCE.createTAnonymousFormalParameter()); + } + applyDefaults(builtInTypeScope, (EObject) setter.getFpar()); + } + + private void applyDefaults(BuiltInTypeScope builtInTypeScope, TStructMethod method) { + if (method.isASTCallSignature()) { + method.setName(N4JSLanguageUtils.CALL_SIGNATURE_NAME); + } + for (TFormalParameter it : method.getFpars()) { + applyDefaults(builtInTypeScope, it); + } + if (method.getReturnTypeRef() == null) { + method.setReturnTypeRef(builtInTypeScope.getVoidTypeRef()); + } + } + + private void applyDefaults(BuiltInTypeScope builtInTypeScope, TFormalParameter fpar) { + if (fpar.getTypeRef() == null) { + fpar.setTypeRef(builtInTypeScope.getAnyTypeRef()); + } + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesFromTypeRefBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesFromTypeRefBuilder.xtend deleted file mode 100644 index 69dc4774cb..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSTypesFromTypeRefBuilder.xtend +++ /dev/null @@ -1,162 +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.typesbuilder; - -import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope -import org.eclipse.n4js.ts.typeRefs.FunctionTypeExpression -import org.eclipse.n4js.ts.typeRefs.StructuralTypeRef -import org.eclipse.n4js.ts.types.TFormalParameter -import org.eclipse.n4js.ts.types.TStructField -import org.eclipse.n4js.ts.types.TStructGetter -import org.eclipse.n4js.ts.types.TStructMember -import org.eclipse.n4js.ts.types.TStructMethod -import org.eclipse.n4js.ts.types.TStructSetter -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.utils.N4JSLanguageUtils - -/** - * Methods for creating types from TypeRefs are collected in this class. - *

- * The methods of this class might seem different from other createXYZ() methods in the types - * builder package, in that they take a subclass of TypeRef and not a typical AST node element. - * However, SturcturalTypeRefs and FunctionTypeExpressions are among those TypeRefs that - * may appear in the AST and play the role of an AST node. The methods in this class - * will only be invoked for such TypeRefs that appear in the AST, so these method are, in fact, - * very similar to the other createXYZ() methods. - */ -public class N4JSTypesFromTypeRefBuilder { - - /** - * Creates a TStructuralType in the target module if the StructuralTypeRef has structural - * members defined (in the with-clause). For more details why this is required, see API - * doc of StructuralTypeRef. - */ - def package void createStructuralType(StructuralTypeRef structTypeRef) { - if (!structTypeRef.astStructuralMembers.empty) { - - val resSet = structTypeRef.eResource.resourceSet; - if(resSet===null) { - throw new IllegalArgumentException("structTypeRef must be contained in AST"); - } - - val builtInTypeScope = BuiltInTypeScope.get(resSet); - val structType = TypesFactory.eINSTANCE.createTStructuralType; - - for (memberInAST : structTypeRef.astStructuralMembers) { - val memberForTModule = createTStructMember(memberInAST, builtInTypeScope); - if (memberInAST.isASTCallSignature()) { - if (structType.callSignature === null) { - structType.callSignature = memberForTModule as TStructMethod; - } else { - // error case: duplicate call signatures - // --> to avoid scoping from returning elements that are not contained in a resource (esp. in case of - // type parameters of generic call signatures), we have to add 'memberForTModule' as an ordinary member - structType.ownedMembers += memberForTModule; - } - } else if (memberInAST.isASTConstructSignature()) { - if (structType.constructSignature === null) { - structType.constructSignature = memberForTModule as TStructMethod; - } else { - // error case: duplicate construct signatures - // --> we have to add 'memberForTModule' as an ordinary member (see above) - structType.ownedMembers += memberForTModule; - } - } else { - structType.ownedMembers += memberForTModule; - } - } - - structTypeRef.structuralType = structType; - } - } - - def private T createTStructMember(T memberInAST, BuiltInTypeScope builtInTypeScope) { - if (memberInAST === null) { - return null; - } - val memberForModule = TypeUtils.copyWithProxies(memberInAST); - applyDefaults(builtInTypeScope, memberForModule); - memberForModule.astElement = memberInAST; - memberInAST.definedMember = memberForModule; - return memberForModule; - } - - - /** - * Creates a TFunction in the target module if the FunctionTypeExpression is generic. - * For more details why this is required, see API doc of FunctionTypeExpression. - */ - def package void createTFunction(FunctionTypeExpression fte) { - - if (fte.generic) { - - val resSet = fte.eResource.resourceSet; - if(resSet===null) { - throw new IllegalArgumentException("fte must be contained in AST"); - } - - val builtInTypeScope = BuiltInTypeScope.get(resSet); - val ft = TypesFactory.eINSTANCE.createTFunction(); - - ft.typeVars.addAll(fte.typeVars.map[currTypeVar|TypeUtils.copyWithProxies(currTypeVar)]); // TODO support hyper links - ft.fpars.addAll(fte.fpars.map[currFpar| - val clone = TypeUtils.copyWithProxies(currFpar); - applyDefaults(builtInTypeScope, clone); - clone.astElement = currFpar; - return clone; - ]); - ft.returnTypeRef = TypeUtils.copyWithProxies(fte.returnTypeRef); - ft.declaredThisType = TypeUtils.copyWithProxies(fte.declaredThisType); - - if(ft.returnTypeRef===null) { - ft.returnTypeRef = builtInTypeScope.getAnyTypeRef(); - } - - fte.declaredType = ft; - ft.astElement = fte; - } - } - - - def private dispatch void applyDefaults(BuiltInTypeScope builtInTypeScope, TStructField field) { - if(field.typeRef===null) { - field.typeRef = builtInTypeScope.getAnyTypeRef(); - } - } - def private dispatch void applyDefaults(BuiltInTypeScope builtInTypeScope, TStructGetter getter) { - if(getter.typeRef===null) { - getter.typeRef = builtInTypeScope.getAnyTypeRef(); - } - } - def private dispatch void applyDefaults(BuiltInTypeScope builtInTypeScope, TStructSetter setter) { - // note: setter.fpar===null and setter.fpar.typeRef===null are disallowed by syntax, but - // setting default types for these cases gives us better behavior in case of broken ASTs - if(setter.fpar===null) { - setter.fpar = TypesFactory.eINSTANCE.createTAnonymousFormalParameter(); - } - applyDefaults(builtInTypeScope, setter.fpar); - } - def private dispatch void applyDefaults(BuiltInTypeScope builtInTypeScope, TStructMethod method) { - if (method.isASTCallSignature) { - method.name = N4JSLanguageUtils.CALL_SIGNATURE_NAME; - } - method.fpars.forEach[applyDefaults(builtInTypeScope, it)]; - if(method.returnTypeRef===null) { - method.returnTypeRef = builtInTypeScope.voidTypeRef - } - } - def private dispatch void applyDefaults(BuiltInTypeScope builtInTypeScope, TFormalParameter fpar) { - if(fpar.typeRef===null) { - fpar.typeRef = builtInTypeScope.getAnyTypeRef(); - } - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSVariableStatementTypesBuilder.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSVariableStatementTypesBuilder.java new file mode 100644 index 0000000000..0503fe27b9 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSVariableStatementTypesBuilder.java @@ -0,0 +1,222 @@ +/** + * 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.typesbuilder; + +import static org.eclipse.xtext.xbase.lib.IterableExtensions.fold; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.N4JSLanguageConstants; +import org.eclipse.n4js.n4JS.ArrowFunction; +import org.eclipse.n4js.n4JS.BindingPattern; +import org.eclipse.n4js.n4JS.CatchVariable; +import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor; +import org.eclipse.n4js.n4JS.NewExpression; +import org.eclipse.n4js.n4JS.ObjectLiteral; +import org.eclipse.n4js.n4JS.TryStatement; +import org.eclipse.n4js.n4JS.VariableDeclaration; +import org.eclipse.n4js.n4JS.VariableDeclarationContainer; +import org.eclipse.n4js.n4JS.VariableStatement; +import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.TVariable; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.utils.ResourceType; + +import com.google.inject.Inject; + +class N4JSVariableStatementTypesBuilder { + + @Inject + N4JSTypesBuilderHelper _n4JSTypesBuilderHelper; + + @Inject + N4JSExportDefinitionTypesBuilder exportDefinitionTypesBuilder; + + int relinkVariableTypes(VariableDeclarationContainer n4VarDeclContainer, AbstractNamespace target, + boolean preLinkingPhase, int start) { + return relinkVariableTypes(n4VarDeclContainer.getVarDecl(), target, preLinkingPhase, start); + } + + int relinkVariableTypes(TryStatement tryStmnt, AbstractNamespace target, boolean preLinkingPhase, int start) { + CatchVariable catchVariable = tryStmnt.getCatch() == null ? null : tryStmnt.getCatch().getCatchVariable(); + if (catchVariable == null) { + return start; + } + + BindingPattern bindingPattern = catchVariable.getBindingPattern(); + if (bindingPattern != null) { + return relinkVariableTypes(bindingPattern.getAllVariableDeclarations(), target, preLinkingPhase, start); + } else { + TVariable tVariable = createVariable(catchVariable); + target.getLocalVariables().add(tVariable); + return start; + } + } + + private int relinkVariableTypes(Iterable n4VarDecls, AbstractNamespace target, + boolean preLinkingPhase, int start) { + return fold(n4VarDecls, start, (idx, decl) -> { + if (relinkVariableType(decl, target, preLinkingPhase, idx)) { + return idx + 1; + } + return idx; + }); + } + + private boolean relinkVariableType(VariableDeclaration n4VariableDeclaration, AbstractNamespace target, + boolean preLinkingPhase, int idx) { + if (n4VariableDeclaration.getName() == null) { + return false; + } + if (!n4VariableDeclaration.isDirectlyExported()) { + // local variables are not serialized, so we have to re-create them during re-linking + TVariable tVariable = createVariable(n4VariableDeclaration, preLinkingPhase); + target.getLocalVariables().add(tVariable); + return false; + } + + TVariable variable = target.getExportedVariables().get(idx); + _n4JSTypesBuilderHelper.ensureEqualName(n4VariableDeclaration, variable); + variable.setAstElement(n4VariableDeclaration); + n4VariableDeclaration.setDefinedVariable(variable); + return true; + } + + void createVariableTypes(VariableDeclarationContainer n4VarDeclContainer, AbstractNamespace target, + boolean preLinkingPhase) { + EList expVars = target.getExportedVariables(); + EList locVars = target.getLocalVariables(); + + boolean isExported = (n4VarDeclContainer instanceof VariableStatement) + ? ((VariableStatement) n4VarDeclContainer).isDirectlyExported() + : false; + + for (VariableDeclaration varDecl : n4VarDeclContainer.getVarDecl()) { + TVariable variable = createVariable(varDecl, preLinkingPhase); + if (variable != null) { + if (isExported) { + String exportedName = varDecl.getDirectlyExportedName(); + exportDefinitionTypesBuilder.createExportDefinitionForDirectlyExportedElement(variable, + exportedName, target, preLinkingPhase); + _n4JSTypesBuilderHelper.setTypeAccessModifier(variable, (VariableStatement) n4VarDeclContainer); + expVars.add(variable); + } else { + if (n4VarDeclContainer instanceof VariableStatement) { // could also be a ForStatement + _n4JSTypesBuilderHelper.setTypeAccessModifier(variable, (VariableStatement) n4VarDeclContainer); + } + locVars.add(variable); + } + } + } + } + + void createVariableTypes(TryStatement tryStmnt, AbstractNamespace target, boolean preLinkingPhase) { + EList locVars = target.getLocalVariables(); + + CatchVariable catchVariable = tryStmnt.getCatch() == null ? null : tryStmnt.getCatch().getCatchVariable(); + if (catchVariable == null) { + return; + } + + BindingPattern bindingPattern = catchVariable.getBindingPattern(); + if (bindingPattern != null) { + for (VariableDeclaration varDecl : bindingPattern.getAllVariableDeclarations()) { + TVariable variable = createVariable(varDecl, preLinkingPhase); + if (variable != null) { + locVars.add(variable); + } + } + } else { + TVariable variable = createVariable(catchVariable); + if (variable != null) { + locVars.add(variable); + } + } + } + + private TVariable createVariable(VariableDeclaration n4VariableDeclaration, boolean preLinkingPhase) { + if (n4VariableDeclaration.getName() == null) { + return null; + } + + TVariable variable = TypesFactory.eINSTANCE.createTVariable(); + variable.setName(n4VariableDeclaration.getName()); + variable.setConst(n4VariableDeclaration.isConst()); + variable.setObjectLiteral(n4VariableDeclaration.getExpression() instanceof ObjectLiteral); + variable.setNewExpression(n4VariableDeclaration.getExpression() instanceof NewExpression); + + _n4JSTypesBuilderHelper.copyAnnotations(variable, n4VariableDeclaration, preLinkingPhase); + variable.setDeclaredProvidedByRuntime( + AnnotationDefinition.PROVIDED_BY_RUNTIME.hasAnnotation(n4VariableDeclaration)); + + // set declared type (if any), otherwise type will be inferred in phase 2 + setVariableType(variable, n4VariableDeclaration, preLinkingPhase); + + variable.setAstElement(n4VariableDeclaration); + n4VariableDeclaration.setDefinedVariable(variable); + + return variable; + } + + private TVariable createVariable(CatchVariable catchVariable) { + if (catchVariable.getName() == null) { + return null; + } + + TVariable variable = TypesFactory.eINSTANCE.createTVariable(); + variable.setName(catchVariable.getName()); + + variable.setAstElement(catchVariable); + catchVariable.setDefinedVariable(variable); + + return variable; + } + + private void setVariableType(TVariable variable, VariableDeclaration n4VariableDeclaration, + boolean preLinkingPhase) { + if (n4VariableDeclaration.getDeclaredTypeRefInAST() != null) { + if (!preLinkingPhase) + // type of field was declared explicitly + variable.setTypeRef(TypeUtils.copyWithProxies(n4VariableDeclaration.getDeclaredTypeRefInAST())); + } else { + // in all other cases: + // leave it to the TypingASTWalker to infer the type (e.g. from the initializer expression, if given) + variable.setTypeRef(TypeUtils.createDeferredTypeRef()); + } + } + + TVariable createImplicitArgumentsVariable(FunctionOrFieldAccessor funOrAccDecl, AbstractNamespace target, + BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { + if (funOrAccDecl instanceof ArrowFunction) { + return null; // not available in arrow functions + } + ResourceType resourceType = ResourceType.getResourceType(funOrAccDecl); + if (resourceType != ResourceType.N4JS && resourceType != ResourceType.N4JSX) { + return null; // not required in definition files + } + + TVariable formalParameterType = TypesFactory.eINSTANCE.createTVariable(); + formalParameterType.setName(N4JSLanguageConstants.LOCAL_ARGUMENTS_VARIABLE_NAME); + + if (!preLinkingPhase) { + formalParameterType.setTypeRef(TypeUtils.createTypeRef(builtInTypeScope.getArgumentsType())); + } + + funOrAccDecl.setImplicitArgumentsVariable(formalParameterType); + formalParameterType.setAstElement(funOrAccDecl); + + target.getLocalVariables().add(formalParameterType); + + return formalParameterType; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSVariableStatementTypesBuilder.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSVariableStatementTypesBuilder.xtend deleted file mode 100644 index 047756d35c..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesbuilder/N4JSVariableStatementTypesBuilder.xtend +++ /dev/null @@ -1,207 +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.typesbuilder - -import com.google.inject.Inject -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.N4JSLanguageConstants -import org.eclipse.n4js.n4JS.ArrowFunction -import org.eclipse.n4js.n4JS.CatchVariable -import org.eclipse.n4js.n4JS.FunctionOrFieldAccessor -import org.eclipse.n4js.n4JS.NewExpression -import org.eclipse.n4js.n4JS.ObjectLiteral -import org.eclipse.n4js.n4JS.TryStatement -import org.eclipse.n4js.n4JS.VariableDeclaration -import org.eclipse.n4js.n4JS.VariableDeclarationContainer -import org.eclipse.n4js.n4JS.VariableStatement -import org.eclipse.n4js.scoping.builtin.BuiltInTypeScope -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.TVariable -import org.eclipse.n4js.ts.types.TypesFactory -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.utils.ResourceType - -package class N4JSVariableStatementTypesBuilder { - - @Inject extension N4JSTypesBuilderHelper - - @Inject N4JSExportDefinitionTypesBuilder exportDefinitionTypesBuilder; - - def package int relinkVariableTypes(VariableDeclarationContainer n4VarDeclContainer, AbstractNamespace target, boolean preLinkingPhase, int start) { - return relinkVariableTypes(n4VarDeclContainer.varDecl, target, preLinkingPhase, start); - } - - def package int relinkVariableTypes(TryStatement tryStmnt, AbstractNamespace target, boolean preLinkingPhase, int start) { - val catchVariable = tryStmnt.^catch?.catchVariable; - if (catchVariable === null) { - return start; - } - - val bindingPattern = catchVariable.bindingPattern; - if (bindingPattern !== null) { - return relinkVariableTypes(bindingPattern.allVariableDeclarations, target, preLinkingPhase, start); - } else { - val tVariable = createVariable(catchVariable, preLinkingPhase); - target.localVariables += tVariable; - return start; - } - } - - def private int relinkVariableTypes(Iterable n4VarDecls, AbstractNamespace target, boolean preLinkingPhase, int start) { - return n4VarDecls.fold(start) [ idx, decl | - if (decl.relinkVariableType(target, preLinkingPhase, idx)) { - return idx + 1; - } - return idx; - ]; - } - - def private boolean relinkVariableType(VariableDeclaration n4VariableDeclaration, AbstractNamespace target, boolean preLinkingPhase, int idx) { - if(n4VariableDeclaration.name === null) { - return false - } - if (!n4VariableDeclaration.directlyExported) { - // local variables are not serialized, so we have to re-create them during re-linking - val tVariable = createVariable(n4VariableDeclaration, preLinkingPhase); - target.localVariables += tVariable; - return false; - } - - val variable = target.exportedVariables.get(idx); - ensureEqualName(n4VariableDeclaration, variable); - variable.astElement = n4VariableDeclaration - n4VariableDeclaration.definedVariable = variable; - return true - } - - def package void createVariableTypes(VariableDeclarationContainer n4VarDeclContainer, AbstractNamespace target, boolean preLinkingPhase) { - val expVars = target.exportedVariables; - val locVars = target.localVariables; - - val isExported = if (n4VarDeclContainer instanceof VariableStatement) n4VarDeclContainer.directlyExported else false; - - for (varDecl : n4VarDeclContainer.varDecl) { - val variable = createVariable(varDecl, preLinkingPhase); - if (variable !== null) { - if (isExported) { - val exportedName = varDecl.directlyExportedName; - exportDefinitionTypesBuilder.createExportDefinitionForDirectlyExportedElement(variable, exportedName, target, preLinkingPhase); - variable.setTypeAccessModifier(n4VarDeclContainer as VariableStatement); - expVars += variable; - } else { - if (n4VarDeclContainer instanceof VariableStatement) { // could also be a ForStatement - variable.setTypeAccessModifier(n4VarDeclContainer); - } - locVars += variable; - } - } - } - } - - def package void createVariableTypes(TryStatement tryStmnt, AbstractNamespace target, boolean preLinkingPhase) { - val locVars = target.localVariables; - - val catchVariable = tryStmnt.^catch?.catchVariable; - if (catchVariable === null) { - return; - } - - val bindingPattern = catchVariable.bindingPattern; - if (bindingPattern !== null) { - for (varDecl : bindingPattern.allVariableDeclarations) { - val variable = createVariable(varDecl, preLinkingPhase); - if (variable !== null) { - locVars += variable; - } - } - } else { - val variable = createVariable(catchVariable, preLinkingPhase); - if (variable !== null) { - locVars += variable; - } - } - } - - def private TVariable createVariable(VariableDeclaration n4VariableDeclaration, boolean preLinkingPhase) { - if(n4VariableDeclaration.name === null) { - return null - } - - val variable = TypesFactory.eINSTANCE.createTVariable - variable.name = n4VariableDeclaration.name; - variable.const = n4VariableDeclaration.const; - variable.objectLiteral = n4VariableDeclaration.expression instanceof ObjectLiteral; - variable.newExpression = n4VariableDeclaration.expression instanceof NewExpression; - - variable.copyAnnotations(n4VariableDeclaration, preLinkingPhase) - variable.declaredProvidedByRuntime = AnnotationDefinition.PROVIDED_BY_RUNTIME.hasAnnotation(n4VariableDeclaration) - - // set declared type (if any), otherwise type will be inferred in phase 2 - setVariableType(variable, n4VariableDeclaration, preLinkingPhase) - - variable.astElement = n4VariableDeclaration - n4VariableDeclaration.definedVariable = variable; - - return variable - } - - def private TVariable createVariable(CatchVariable catchVariable, boolean preLinkingPhase) { - if (catchVariable.name === null) { - return null; - } - - val variable = TypesFactory.eINSTANCE.createTVariable - variable.name = catchVariable.name; - - variable.astElement = catchVariable; - catchVariable.definedVariable = variable; - - return variable - } - - def private void setVariableType(TVariable variable, VariableDeclaration n4VariableDeclaration, boolean preLinkingPhase) { - if(n4VariableDeclaration.declaredTypeRefInAST!==null) { - if (!preLinkingPhase) - // type of field was declared explicitly - variable.typeRef = TypeUtils.copyWithProxies(n4VariableDeclaration.declaredTypeRefInAST); - } - else { - // in all other cases: - // leave it to the TypingASTWalker to infer the type (e.g. from the initializer expression, if given) - variable.typeRef = TypeUtils.createDeferredTypeRef - } - } - - - def package TVariable createImplicitArgumentsVariable(FunctionOrFieldAccessor funOrAccDecl, AbstractNamespace target, BuiltInTypeScope builtInTypeScope, boolean preLinkingPhase) { - if (funOrAccDecl instanceof ArrowFunction) { - return null; // not available in arrow functions - } - val resourceType = ResourceType.getResourceType(funOrAccDecl); - if (resourceType !== ResourceType.N4JS && resourceType !== ResourceType.N4JSX) { - return null; // not required in definition files - } - - val formalParameterType = TypesFactory::eINSTANCE.createTVariable(); - formalParameterType.name = N4JSLanguageConstants.LOCAL_ARGUMENTS_VARIABLE_NAME; - - if (!preLinkingPhase) { - formalParameterType.typeRef = TypeUtils.createTypeRef(builtInTypeScope.argumentsType); - } - - funOrAccDecl.implicitArgumentsVariable = formalParameterType; - formalParameterType.astElement = funOrAccDecl; - - target.localVariables += formalParameterType; - - return formalParameterType; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/N4JSImportValidator.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/N4JSImportValidator.xtend index b5204fbda8..5a2b8422b5 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/N4JSImportValidator.xtend +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/N4JSImportValidator.xtend @@ -340,7 +340,7 @@ class N4JSImportValidator extends AbstractN4JSDeclarativeValidator { private def handleNotImportedTypeRefs(Script script, List specifiersWithIssues, Map eObjectToIssueCode) { - val importedProvidedElementsWithIssuesByModule = specifiersWithIssues.mapToImportProvidedElements(jsVariantHelper).groupBy [ + val importedProvidedElementsWithIssuesByModule = mapToImportProvidedElements(specifiersWithIssues).groupBy [ importedModule ] val potentiallyAffectedTypeRefs = script.eAllContents.filter(ParameterizedTypeRef).filter [ diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/N4JSLambdaValidator.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/N4JSLambdaValidator.java index e840fed752..f29ee81369 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/N4JSLambdaValidator.java +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/N4JSLambdaValidator.java @@ -14,6 +14,7 @@ import org.eclipse.emf.ecore.EObject; import org.eclipse.n4js.n4JS.ArrowFunction; +import org.eclipse.n4js.n4JS.ThisLiteral; import org.eclipse.n4js.types.utils.LambdaUtils; import org.eclipse.n4js.utils.ContainerTypesHelper; import org.eclipse.n4js.validation.AbstractN4JSDeclarativeValidator; @@ -60,7 +61,7 @@ public void checkTopLevelLambda(ArrowFunction arrowFun) { */ private void rejectUsagesOfThisInTopLevelLambda(ArrowFunction topLevelLambda) { assert LambdaUtils.isLambda(topLevelLambda); - Iterator 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);