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..d36a6f71b9 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/ASTProcessor.java @@ -0,0 +1,562 @@ +/** + * 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.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: + * + */ +@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: + *

+ */ + 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(), List.of(((SetterDeclaration) obj).getFpar())); + } + if (obj instanceof FunctionDefinition) { + // process formal parameters before body + return bringToFront(obj.eContents(), ((FunctionDefinition) obj).getFpars()); + } + if (obj instanceof CatchBlock) { + // process catch variable before block + return bringToFront(obj.eContents(), List.of(((CatchBlock) obj).getCatchVariable())); + } + if (obj instanceof ForStatement) { + // process expression before varDeclOrBindings + return bringToFront(obj.eContents(), List.of(((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, List elements) { + List result = new ArrayList<>(l); + List elemSanitized = toList(filterNull(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 212c8656d2..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); - } - for(node : script.eAllContents.filter(LiteralOrComputedPropertyName).toIterable) { - computedNameProcessor.processComputedPropertyName( node, cache ); - } - - // 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/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/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/TypeProcessor.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeProcessor.xtend index f5a5de20b2..4106ec3003 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeProcessor.xtend +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/TypeProcessor.xtend @@ -83,7 +83,7 @@ public class TypeProcessor extends AbstractProcessor { && 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); + destructureProcessor.typeDestructuringPattern(G, node, cache ); } else { // standard case