diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/utils/DestructureHelper.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/utils/DestructureHelper.java
new file mode 100644
index 0000000000..fc98dd83eb
--- /dev/null
+++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/utils/DestructureHelper.java
@@ -0,0 +1,471 @@
+/**
+ * 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.utils;
+
+import static org.eclipse.n4js.n4JS.DestructNode.arePositional;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.arrayTypeRef;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.iterableNTypeRef;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.iterableTypeRef;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.objectType;
+import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.wrap;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.n4js.n4JS.ArrayLiteral;
+import org.eclipse.n4js.n4JS.AssignmentExpression;
+import org.eclipse.n4js.n4JS.DestructNode;
+import org.eclipse.n4js.n4JS.DestructureUtils;
+import org.eclipse.n4js.n4JS.Expression;
+import org.eclipse.n4js.n4JS.ForStatement;
+import org.eclipse.n4js.n4JS.IdentifierRef;
+import org.eclipse.n4js.n4JS.ObjectLiteral;
+import org.eclipse.n4js.n4JS.TypeReferenceNode;
+import org.eclipse.n4js.n4JS.TypedElement;
+import org.eclipse.n4js.n4JS.VariableBinding;
+import org.eclipse.n4js.n4JS.VariableDeclaration;
+import org.eclipse.n4js.postprocessing.ASTProcessor;
+import org.eclipse.n4js.scoping.accessModifiers.VisibilityAwareMemberScope;
+import org.eclipse.n4js.scoping.members.MemberScopingHelper;
+import org.eclipse.n4js.scoping.utils.AbstractDescriptionWithError;
+import org.eclipse.n4js.ts.typeRefs.TypeArgument;
+import org.eclipse.n4js.ts.typeRefs.TypeRef;
+import org.eclipse.n4js.ts.types.IdentifiableElement;
+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.TStructField;
+import org.eclipse.n4js.ts.types.TStructMember;
+import org.eclipse.n4js.ts.types.TVariable;
+import org.eclipse.n4js.ts.types.TypesFactory;
+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.constraints.InferenceContext;
+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 com.google.common.base.Optional;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * Helper for dealing with destructuring patterns. For more details on destructuring patterns, see documentation of
+ * class {@link DestructNode}.
+ */
+@Singleton
+public class DestructureHelper {
+
+ @Inject
+ private N4JSTypeSystem ts;
+ @Inject
+ private TypeSystemHelper tsh;
+ @Inject
+ private MemberScopingHelper memberScopingHelper;
+
+ /**
+ * Infers the type of a variable declaration within a destructuring pattern from the value to be destructured and/or
+ * the default value given in the pattern.
+ *
+ * Returns null
if vdecl
has an explicitly declared type or in case of error.
+ */
+ public TypeRef getTypeOfVariableDeclarationInDestructuringPattern(RuleEnvironment G, VariableDeclaration vdecl) {
+ if (vdecl.getDeclaredTypeRef() != null) {
+ return null;
+ }
+
+ EObject root = DestructureUtils.getRoot(vdecl.eContainer());
+ if (root == null) {
+ return null;
+ }
+
+ EObject rootParent = root.eContainer();
+ if (rootParent instanceof VariableBinding) {
+
+ EObject rootParent2 = rootParent.eContainer();
+ boolean isLocatedUnderForInOf = rootParent2 instanceof ForStatement
+ && DestructureUtils.isTopOfDestructuringForStatement(rootParent2);
+
+ DestructNode rootNode = (isLocatedUnderForInOf)
+ ? DestructNode.unify((ForStatement) rootParent2)
+ : DestructNode.unify(rootParent);
+
+ if (rootNode != null) {
+
+ Set astNodesToConsider = EcoreUtilN4.getAllPredecessorsUpTo(vdecl, root);
+ astNodesToConsider.add(vdecl);
+
+ Map valueTypePerNode = new HashMap<>();
+ buildValueTypesMap(G, rootNode, Optional.of(astNodesToConsider), valueTypePerNode,
+ ((VariableBinding) rootParent).getPattern());
+ DestructNode node = rootNode.stream()
+ .filter(it -> it.getVariableDeclaration() == vdecl)
+ .findFirst()
+ .orElse(null);
+ return valueTypePerNode.get(node);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * For a unified destructuring pattern created with one of the unify() methods in {@link DestructNode}, this method
+ * will return a mapping of the pattern's nodes to their value type, i.e. to the type of their value to be
+ * destructured. The returned map might not contain key/value pairs for all nodes (in case of error) but won't
+ * contain null
values.
+ *
+ * @param G
+ * rule environment (used for type inference).
+ * @param rootNode
+ * root of the destructuring pattern.
+ * @param astNodesToConsider
+ * if present, only {@link DestructNode}s are processed that have a {@link DestructNode#astElement
+ * corresponding AST node} contained in this set or do not have a corresponding AST node at all; if
+ * absent, all {@code DestructNode}s are processed. The given root node is always processed.
+ * @param addHere
+ * types for each node will be added here.
+ * @param contextObj
+ * context object (used for member scoping and obtaining the containing resource or resource set).
+ */
+ public void buildValueTypesMap(RuleEnvironment G, DestructNode rootNode, Optional> astNodesToConsider,
+ Map addHere, EObject contextObj) {
+
+ if (rootNode == null || rootNode.defaultExpr == null) {
+ return;
+ }
+ // in the root node, the defaultExpr is the main value to be destructured and
+ // the node's type is simply the type of that value (nothing needs to be destructured yet)
+ var valueTypeRef = ts.type(G, rootNode.defaultExpr);
+ valueTypeRef = ts.upperBoundWithReopenAndResolveBoth(G, valueTypeRef);
+ // special case: ForStatement
+ // we might have something like for([a,b] of expr){} (in which case rootNode.defaultExpr points to the 'expr')
+ if (rootNode.defaultExpr.eContainer() instanceof ForStatement) {
+ // valueTypeRef is currently the type of 'expr' and thus something like Iterable
+ // -> we are interested in the T
+ valueTypeRef = tsh.extractIterableElementType(G, valueTypeRef, false);
+ }
+ // now, valueTypeRef is the type of the root value to be destructured by rootNode.nestedNodes (or null if
+ // invalid)
+ if (valueTypeRef != null) {
+ addHere.put(rootNode, valueTypeRef);
+ DestructNode[] nestedNodes = rootNode.nestedNodes;
+ if (nestedNodes != null && nestedNodes.length != 0) {
+ buildValueTypesMap(G, nestedNodes, valueTypeRef, astNodesToConsider, addHere, contextObj);
+ }
+ }
+ }
+
+ private void buildValueTypesMap(RuleEnvironment G, DestructNode[] nodes, TypeRef valueTypeRef,
+ Optional> astNodesToConsider, Map addHere, EObject contextObj) {
+
+ // for all non-root nodes, we have to destructure the type of the value,
+ // depending on whether we are in an array or object destructuring (sub-)pattern
+ if (arePositional(Arrays.asList(nodes))) {
+ // positional
+ List elementTypeRefs = tsh.extractIterableElementTypes(G, valueTypeRef);
+ if (!elementTypeRefs.isEmpty()) {
+ int maxIdx = elementTypeRefs.size() - 1;
+ for (var idx = 0; idx < nodes.length; idx++) {
+ DestructNode currNode = nodes[idx];
+ if (isToBeConsidered(currNode, astNodesToConsider)) {
+ TypeRef currValueTypeRef = elementTypeRefs.get(Math.min(idx, maxIdx));
+ addTypeAndContinueWithChildren(G, currNode, currValueTypeRef, astNodesToConsider, addHere,
+ contextObj);
+ }
+ }
+ }
+ } else {
+ // non-positional
+ IScope memberScope = createMemberScopeForPropertyAccess(valueTypeRef, contextObj, false); // do not check
+ // visibility
+ for (DestructNode currNode : nodes) {
+ if (isToBeConsidered(currNode, astNodesToConsider)) {
+ TypeRef currValueTypeRef = getPropertyTypeForNode(G, valueTypeRef, memberScope, currNode.propName,
+ null);
+ if (currValueTypeRef != null) {
+ addTypeAndContinueWithChildren(G, currNode, currValueTypeRef, astNodesToConsider, addHere,
+ contextObj);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Create a new member scope for use with method
+ * {@link #getPropertyTypeForNode(RuleEnvironment, TypeRef, IScope, String, AtomicReference)
+ * #getPropertyTypeForNode()}. Do not use the scope returned by this method for any other purpose (use methods in
+ * {@link MemberScopingHelper} instead)!
+ *
+ * This is only provided as a separate method to avoid creating the same member scope over and over in case of
+ * multiple invocations of {@code #getPropertyTypeForNode()}.
+ *
+ * @param receiverTypeRef
+ * type of the value to be destructured.
+ * @param contextObj
+ * context object used for (a) obtaining context resource and (b) visibility checking.
+ * @param checkVisibility
+ * if true, the member scope will be wrapped in a {@link VisibilityAwareMemberScope}; if false, method
+ * {@link #getPropertyTypeForNode(RuleEnvironment, TypeRef, IScope, String, AtomicReference)} will
+ * never return INVISIBLE_MEMBER.
+ */
+ public IScope createMemberScopeForPropertyAccess(TypeRef receiverTypeRef, EObject contextObj,
+ boolean checkVisibility) {
+ boolean structFieldInitMode = receiverTypeRef
+ .getTypingStrategy() == TypingStrategy.STRUCTURAL_FIELD_INITIALIZER;
+ return memberScopingHelper.createMemberScopeAllowingNonContainedMembers(receiverTypeRef, contextObj,
+ checkVisibility, false, structFieldInitMode);
+ }
+
+ /**
+ * Returns type of a property within an object destructuring pattern or null
if property does not
+ * exist. In case the property exists but is not available (e.g. not visible), an error message is appended to given
+ * StringBuffer 'errorMessage' (optional).
+ *
+ * @param parentValueTypeRef
+ * value type of the parent node.
+ * @param parentMemberScope
+ * a member scope as returned by method
+ * {@link #createMemberScopeForPropertyAccess(TypeRef,EObject,boolean)}.
+ * @param propName
+ * name of property to look up.
+ */
+ public TypeRef getPropertyTypeForNode(RuleEnvironment G, TypeRef parentValueTypeRef, IScope parentMemberScope,
+ String propName, AtomicReference mDescRef) {
+ if (parentValueTypeRef == null || parentMemberScope == null) {
+ return null;
+ }
+ IEObjectDescription mDesc = parentMemberScope.getSingleElement(QualifiedName.create(propName));
+ if (mDesc instanceof AbstractDescriptionWithError) {
+ if (mDescRef != null) {
+ mDescRef.set((AbstractDescriptionWithError) mDesc);
+ }
+ }
+ EObject m = mDesc == null ? null : mDesc.getEObjectOrProxy();
+ if (m != null && !m.eIsProxy()) {
+ TypeRef result = null;
+ if (m instanceof TField) {
+ result = ((TField) m).getTypeRef();
+ }
+ if (m instanceof TGetter) {
+ result = ((TGetter) m).getTypeRef();
+ }
+ if (result != null) {
+ // substitute type variables in 'result'
+ RuleEnvironment G2 = wrap(G);
+ tsh.addSubstitutions(G2, parentValueTypeRef);
+ TypeRef resultSubst = ts.substTypeVariables(G2, result);
+ return resultSubst;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Following code factored out from #buildTypesMap(), because it is common to the positional and non-positional
+ * case. IMPORTANT: this method must also be called if valueTypeRef==null, because there might be a default
+ * expression in 'currNode' that provides a type.
+ */
+ private void addTypeAndContinueWithChildren(RuleEnvironment G, DestructNode currNode, TypeRef valueTypeRef,
+ Optional> astNodesToConsider, Map addHere, EObject contextObj) {
+
+ TypeRef actualValueTypeRef = (currNode.rest)
+ ? arrayTypeRef(G, valueTypeRef) // wrap in Array<> if rest operator used
+ : valueTypeRef;
+ TypeRef currTypeRef = mergeWithTypeOfDefaultExpression(G, actualValueTypeRef, currNode);
+ if (currTypeRef != null) {
+ // add type of currNode
+ addHere.put(currNode, currTypeRef);
+ // continue with children of currNode
+ if (currNode.nestedNodes != null && currNode.nestedNodes.length != 0) {
+ // TODO should do this also if currTypeRef==null (because lower-level default expressions may provide
+ // further types)
+ buildValueTypesMap(G, currNode.nestedNodes, currTypeRef, astNodesToConsider, addHere, contextObj);
+ }
+ }
+ }
+
+ /**
+ * Infers type of the default expression of 'currNode' and merges it with the given valueTypeRef. Both the given
+ * value type and inferred expression type may be null and then this returns null.
+ */
+ private TypeRef mergeWithTypeOfDefaultExpression(RuleEnvironment G, TypeRef valueTypeRef, DestructNode node) {
+ if (node.defaultExpr instanceof ObjectLiteral || node.defaultExpr instanceof ArrayLiteral) {
+ // Example: const { prop = {} } = new C1(); // type should be C2; with: class C1 { prop : C2 }
+ return valueTypeRef;
+ }
+ TypeRef exprTypeRef = null;
+ if (node.defaultExpr != null) {
+ exprTypeRef = ts.type(G, node.defaultExpr);
+ }
+ if (valueTypeRef != null && exprTypeRef != null) {
+ // we have to merge the two types ...
+ // (the small optimization with the subtype checks should be done by #createUnionType(), but isn't)
+ if (ts.subtypeSucceeded(G, valueTypeRef, exprTypeRef)) {
+ return exprTypeRef;
+ } else if (ts.subtypeSucceeded(G, exprTypeRef, valueTypeRef)) {
+ return valueTypeRef;
+ } else {
+ return tsh.createUnionType(G, valueTypeRef, exprTypeRef);
+ }
+ } else if (valueTypeRef != null) {
+ return valueTypeRef;
+ } else if (exprTypeRef != null) {
+ return exprTypeRef;
+ }
+ return null;
+ }
+
+ private boolean isToBeConsidered(DestructNode node, Optional> astNodesToConsider) {
+ return node != null
+ && (node.astElement == null || !astNodesToConsider.isPresent()
+ || astNodesToConsider.get().contains(node.astElement));
+ }
+
+ /**
+ * Return the expected type of a poly expression if it is used in a destructure pattern and null otherwise.
+ */
+ public TypeRef calculateExpectedType(Expression rootPoly, RuleEnvironment G, InferenceContext infCtx) {
+ // In case of destructure pattern, we can calculate the expected type based on the structure of the destructure
+ // pattern.
+ DestructNode rootDestructNode = null;
+ if (rootPoly.eContainer() instanceof VariableBinding) {
+ rootDestructNode = DestructNode.unify((VariableBinding) rootPoly.eContainer());
+ } else if (rootPoly.eContainer() instanceof AssignmentExpression) {
+ rootDestructNode = DestructNode.unify((AssignmentExpression) rootPoly.eContainer());
+ } else if (rootPoly.eContainer() instanceof ForStatement) {
+ rootDestructNode = DestructNode.unify((ForStatement) rootPoly.eContainer());
+ }
+ if (rootDestructNode == null) {
+ return null;
+ }
+ return calculateExpectedType(rootDestructNode, G, infCtx);
+ }
+
+ /**
+ * Calculate expected type of a destructure pattern based on its structure.
+ */
+ private TypeRef calculateExpectedType(DestructNode destructNode, RuleEnvironment G, InferenceContext infCtx) {
+ List elementTypes = new ArrayList<>();
+ List elementMembers = new ArrayList<>();
+ int elemCount = destructNode.nestedNodes.length;
+ for (DestructNode nestedNode : destructNode.nestedNodes) {
+ TypeRef elemExpectedType = (nestedNode.nestedNodes != null && nestedNode.nestedNodes.length > 0)
+ ? // Recursively calculate the expected type of the nested child
+ calculateExpectedType(nestedNode, G, infCtx)
+ :
+ // Extract type of leaf node
+ createTypeFromLeafDestructNode(nestedNode, G);
+
+ if (nestedNode.propName != null) {
+ // We are dealing with object literals, hence create TStructMembers to construct a
+ // ParameterizedTypeRefStructural.
+ TStructField field = TypesFactory.eINSTANCE.createTStructField();
+ field.setName(nestedNode.propName);
+ if (elemExpectedType != null) {
+ field.setTypeRef(TypeUtils.copyIfContained(elemExpectedType));
+ } else {
+ // If the expected type is not specified, the expected type is arbitrary hence return a new
+ // inference variable.
+ InferenceVariable iv = infCtx.newInferenceVariable();
+ field.setTypeRef(TypeUtils.createTypeRef(iv));
+ }
+ elementMembers.add(field);
+ } else {
+ if (elemExpectedType != null) {
+ elementTypes.add(elemExpectedType);
+ } else {
+ // If the expected type is not specified, the expected type is arbitrary hence return a new
+ // inference variable.
+ InferenceVariable iv = infCtx.newInferenceVariable();
+ elementTypes.add(TypeUtils.createTypeRef(iv));
+ }
+ }
+ }
+
+ TypeRef retTypeRef = null;
+
+ if (elementMembers.size() > 0) {
+ if (elementTypes.size() > 0) {
+ throw new IllegalStateException(
+ "elementTypes and elementMembers can not both contain elements at the same time.");
+ }
+ retTypeRef = TypeUtils.createParameterizedTypeRefStructural(objectType(G), TypingStrategy.STRUCTURAL,
+ elementMembers.toArray(new TStructMember[0]));
+ } else if (elementTypes.size() > 0) {
+ if (elemCount == 1) {
+ retTypeRef = arrayTypeRef(G, elementTypes.get(0));
+ } else if (elemCount > 1) {
+ retTypeRef = iterableNTypeRef(G, elemCount, elementTypes.toArray(new TypeArgument[0]));
+ }
+ }
+ // Wrap the expected type in an Iterable type in case of ForStatement
+ // Note that we wrap the type into an Iterable type so that when a constraint G <: Iterable<...> is
+ // created,
+ // we would like to reduce it to IV <:..
+ if (retTypeRef != null && DestructureUtils.isTopOfDestructuringForStatement(destructNode.astElement)) {
+ retTypeRef = iterableTypeRef(G, retTypeRef);
+ }
+ return retTypeRef;
+ }
+
+ /** Create expected type for a leaf DestructNode */
+ private TypeRef createTypeFromLeafDestructNode(DestructNode leafNode, RuleEnvironment G) {
+ VariableDeclaration varDecl = leafNode.varDecl;
+ IdentifierRef varRef = leafNode.varRef;
+ if (varDecl != null) {
+ // If it is a variable declaration, simply retrieve the declared type
+ TypeRef declTypeRef = getDeclaredTypeRefOfVarDecl(G, varDecl);
+ if (declTypeRef != null) {
+ return declTypeRef;
+ }
+ } else if (varRef != null) {
+ // It is a variable reference, retrieve the (declared or inferred) type of the variable
+ IdentifiableElement id = varRef.getId();
+ if (id instanceof VariableDeclaration || id instanceof TVariable) {
+ return ts.type(G, id);
+ }
+ }
+ // In case the expected type does not exist, simply return null
+ return null;
+ }
+
+ /**
+ * This is invoked from {@code PolyProcessor#inferType(RuleEnvironment, Expression, ASTMetaInfoCache)} and in case
+ * of for-of loops it may inspect its variable declaration before {@code TypeRefProcessor} has processed that
+ * variable declaration (see order defined for ForStatement in {@link ASTProcessor#childrenToBeProcessed(EObject)}).
+ * Therefore, we cannot rely on {@link TypedElement#getDeclaredTypeRef()} here.
+ */
+ private TypeRef getDeclaredTypeRefOfVarDecl(RuleEnvironment G, VariableDeclaration varDecl) {
+ TypeReferenceNode typeRefNode = varDecl.getDeclaredTypeRefNode();
+ if (typeRefNode != null) {
+ TypeRef cached = typeRefNode.getCachedProcessedTypeRef();
+ if (cached != null) {
+ return cached;
+ }
+ TypeRef inAST = typeRefNode.getTypeRefInAST();
+ if (inAST != null) {
+ return tsh.resolveTypeAliases(G, inAST);
+ }
+ }
+ return null;
+ }
+}
diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/utils/DestructureHelper.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/utils/DestructureHelper.xtend
deleted file mode 100644
index c75947f07f..0000000000
--- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/utils/DestructureHelper.xtend
+++ /dev/null
@@ -1,435 +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.utils
-
-import com.google.common.base.Optional
-import com.google.inject.Inject
-import com.google.inject.Singleton
-import java.util.ArrayList
-import java.util.Map
-import java.util.Set
-import java.util.concurrent.atomic.AtomicReference
-import org.eclipse.emf.ecore.EObject
-import org.eclipse.n4js.n4JS.AssignmentExpression
-import org.eclipse.n4js.n4JS.DestructNode
-import org.eclipse.n4js.n4JS.DestructureUtils
-import org.eclipse.n4js.n4JS.Expression
-import org.eclipse.n4js.n4JS.ForStatement
-import org.eclipse.n4js.n4JS.TypedElement
-import org.eclipse.n4js.n4JS.VariableBinding
-import org.eclipse.n4js.n4JS.VariableDeclaration
-import org.eclipse.n4js.postprocessing.ASTProcessor
-import org.eclipse.n4js.scoping.accessModifiers.VisibilityAwareMemberScope
-import org.eclipse.n4js.scoping.members.MemberScopingHelper
-import org.eclipse.n4js.scoping.utils.AbstractDescriptionWithError
-import org.eclipse.n4js.ts.typeRefs.TypeArgument
-import org.eclipse.n4js.ts.typeRefs.TypeRef
-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.TVariable
-import org.eclipse.n4js.ts.types.TypesFactory
-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.constraints.InferenceContext
-import org.eclipse.n4js.typesystem.utils.RuleEnvironment
-import org.eclipse.n4js.typesystem.utils.TypeSystemHelper
-import org.eclipse.xtext.naming.QualifiedName
-import org.eclipse.xtext.scoping.IScope
-
-import static extension org.eclipse.n4js.n4JS.DestructNode.arePositional
-import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.*
-import org.eclipse.n4js.n4JS.ArrayLiteral
-import org.eclipse.n4js.n4JS.ObjectLiteral
-
-/**
- * Helper for dealing with destructuring patterns. For more details on destructuring patterns,
- * see documentation of class {@link DestructNode}.
- */
-@Singleton
-class DestructureHelper {
-
- @Inject private N4JSTypeSystem ts;
- @Inject private TypeSystemHelper tsh;
- @Inject private MemberScopingHelper memberScopingHelper;
-
- /**
- * Infers the type of a variable declaration within a destructuring pattern from the value to be
- * destructured and/or the default value given in the pattern.
- *
- * Returns null
if vdecl
has an explicitly declared type or in case of error.
- */
- public def TypeRef getTypeOfVariableDeclarationInDestructuringPattern(RuleEnvironment G, VariableDeclaration vdecl) {
- if(vdecl.declaredTypeRef!==null)
- return null;
-
- val root = DestructureUtils.getRoot(vdecl.eContainer);
- if(root===null)
- return null;
-
- val rootParent = root.eContainer;
- if(rootParent instanceof VariableBinding) {
-
- val rootParent2 = rootParent.eContainer;
- val isLocatedUnderForInOf = rootParent2 instanceof ForStatement
- && DestructureUtils.isTopOfDestructuringForStatement(rootParent2);
-
- val rootNode = if(isLocatedUnderForInOf) {
- DestructNode.unify(rootParent2 as ForStatement)
- } else {
- DestructNode.unify(rootParent);
- };
-
- if(rootNode!==null) {
-
- val astNodesToConsider = EcoreUtilN4.getAllPredecessorsUpTo(vdecl, root);
- astNodesToConsider.add(vdecl);
-
- val valueTypePerNode = newHashMap;
- buildValueTypesMap(G, rootNode, Optional.of(astNodesToConsider), valueTypePerNode, rootParent.pattern);
- val node = rootNode.stream().filter[variableDeclaration===vdecl].findFirst.orElse(null);
- return valueTypePerNode.get(node);
- }
- }
-
- return null;
- }
-
-
- /**
- * For a unified destructuring pattern created with one of the unify() methods in {@link DestructNode}, this method will
- * return a mapping of the pattern's nodes to their value type, i.e. to the type of their value to be destructured.
- * The returned map might not contain key/value pairs for all nodes (in case of error) but won't contain null
- * values.
- *
- * @param G rule environment (used for type inference).
- * @param rootNode root of the destructuring pattern.
- * @param astNodesToConsider if present, only {@link DestructNode}s are processed that have a {@link DestructNode#astElement
- * corresponding AST node} contained in this set or do not have a corresponding AST node at all;
- * if absent, all {@code DestructNode}s are processed. The given root node is always processed.
- * @param addHere types for each node will be added here.
- * @param contextObj context object (used for member scoping and obtaining the containing resource or resource set).
- */
- public def void buildValueTypesMap(RuleEnvironment G, DestructNode rootNode, Optional> astNodesToConsider,
- Map addHere, EObject contextObj) {
-
- if(rootNode?.defaultExpr===null) {
- return;
- }
- // in the root node, the defaultExpr is the main value to be destructured and
- // the node's type is simply the type of that value (nothing needs to be destructured yet)
- var valueTypeRef = ts.type(G,rootNode.defaultExpr);
- valueTypeRef = ts.upperBoundWithReopenAndResolveBoth(G, valueTypeRef);
- // special case: ForStatement
- // we might have something like for([a,b] of expr){} (in which case rootNode.defaultExpr points to the 'expr')
- if(rootNode.defaultExpr.eContainer instanceof ForStatement) {
- // valueTypeRef is currently the type of 'expr' and thus something like Iterable
- // -> we are interested in the T
- valueTypeRef = tsh.extractIterableElementType(G, valueTypeRef, false);
- }
- // now, valueTypeRef is the type of the root value to be destructured by rootNode.nestedNodes (or null if invalid)
- if(valueTypeRef!==null) {
- addHere.put(rootNode, valueTypeRef);
- val nestedNodes = rootNode.nestedNodes;
- if(nestedNodes!==null && !nestedNodes.empty) {
- buildValueTypesMap(G, nestedNodes, valueTypeRef, astNodesToConsider, addHere, contextObj);
- }
- }
- }
-
- private def void buildValueTypesMap(RuleEnvironment G, DestructNode[] nodes, TypeRef valueTypeRef,
- Optional> astNodesToConsider, Map addHere, EObject contextObj) {
-
- // for all non-root nodes, we have to destructure the type of the value,
- // depending on whether we are in an array or object destructuring (sub-)pattern
- if(nodes.arePositional) {
- // positional
- val elementTypeRefs = tsh.extractIterableElementTypes(G,valueTypeRef);
- if(!elementTypeRefs.empty) {
- val maxIdx = elementTypeRefs.size-1;
- for(var idx=0;idx
- * This is only provided as a separate method to avoid creating the same member scope over and over in case of
- * multiple invocations of {@code #getPropertyTypeForNode()}.
- *
- * @param valueTypeRef
- * type of the value to be destructured.
- * @param contextObj
- * context object used for (a) obtaining context resource and (b) visibility checking.
- * @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}.
- */
- public def IScope createMemberScopeForPropertyAccess(TypeRef receiverTypeRef, EObject contextObj, boolean checkVisibility) {
- val structFieldInitMode = receiverTypeRef.typingStrategy === TypingStrategy.STRUCTURAL_FIELD_INITIALIZER;
- return memberScopingHelper.createMemberScopeAllowingNonContainedMembers(receiverTypeRef, contextObj,
- checkVisibility, false, structFieldInitMode);
- }
-
- /**
- * Returns type of a property within an object destructuring pattern or null
if property does not exist.
- * In case the property exists but is not available (e.g. not visible), an error message is appended to given
- * StringBuffer 'errorMessage' (optional).
- *
- * @param parentValueTypeRef
- * value type of the parent node.
- * @param parentMemberScope
- * a member scope as returned by method {@link #createMemberScopeForPropertyAccess(TypeRef,EObject,boolean)}.
- * @param propName
- * name of property to look up.
- * @param errorMessage
- * a string buffer where the error message will be stored in case the property exists but is not readable
- * or null
if the caller is not interested in receiving error messages.
- */
- public def TypeRef getPropertyTypeForNode(RuleEnvironment G, TypeRef parentValueTypeRef, IScope parentMemberScope, String propName, AtomicReference mDescRef) {
- if(parentValueTypeRef===null || parentMemberScope===null) {
- return null;
- }
- val mDesc = parentMemberScope.getSingleElement(QualifiedName.create(propName));
- if(mDesc instanceof AbstractDescriptionWithError) {
- if(mDescRef!==null) {
- mDescRef.set(mDesc);
- }
- }
- val m = mDesc?.getEObjectOrProxy();
- if(m!==null && !m.eIsProxy) {
- val result = switch(m) {
- TField: m.typeRef
- TGetter: m.typeRef
- };
- if(result!==null) {
- // substitute type variables in 'result'
- val G2 = G.wrap;
- tsh.addSubstitutions(G2, parentValueTypeRef);
- val resultSubst = ts.substTypeVariables(G2, result);
- return resultSubst;
- }
- }
- return null;
- }
-
- /**
- * Following code factored out from #buildTypesMap(), because it is common to the positional
- * and non-positional case. IMPORTANT: this method must also be called if valueTypeRef===null,
- * because there might be a default expression in 'currNode' that provides a type.
- */
- private def void addTypeAndContinueWithChildren(RuleEnvironment G, DestructNode currNode, TypeRef valueTypeRef,
- Optional> astNodesToConsider, Map addHere, EObject contextObj) {
-
- val actualValueTypeRef = if(currNode.rest) {
- G.arrayTypeRef(valueTypeRef) // wrap in Array<> if rest operator used
- } else {
- valueTypeRef
- };
- val currTypeRef = mergeWithTypeOfDefaultExpression(G,actualValueTypeRef,currNode);
- if(currTypeRef!==null) {
- // add type of currNode
- addHere.put(currNode,currTypeRef);
- // continue with children of currNode
- if(currNode.nestedNodes!==null && !currNode.nestedNodes.empty) {
- // TODO should do this also if currTypeRef===null (because lower-level default expressions may provide further types)
- buildValueTypesMap(G, currNode.nestedNodes, currTypeRef, astNodesToConsider, addHere, contextObj);
- }
- }
- }
-
- /**
- * Infers type of the default expression of 'currNode' and merges it with the given valueTypeRef.
- * Both the given value type and inferred expression type may be null and then this returns null.
- */
- private def TypeRef mergeWithTypeOfDefaultExpression(RuleEnvironment G, TypeRef valueTypeRef, DestructNode node) {
- if (node.defaultExpr instanceof ObjectLiteral || node.defaultExpr instanceof ArrayLiteral) {
- // Example: const { prop = {} } = new C1(); // type should be C2; with: class C1 { prop : C2 }
- return valueTypeRef;
- }
- val exprTypeRef = if(node.defaultExpr!==null) ts.type(G, node.defaultExpr);
- if(valueTypeRef!==null && exprTypeRef!==null) {
- // we have to merge the two types ...
- // (the small optimization with the subtype checks should be done by #createUnionType(), but isn't)
- return if(ts.subtypeSucceeded(G,valueTypeRef,exprTypeRef)) {
- exprTypeRef
- } else if(ts.subtypeSucceeded(G,exprTypeRef,valueTypeRef)) {
- valueTypeRef
- } else {
- tsh.createUnionType(G, valueTypeRef, exprTypeRef)
- };
- }
- else if(valueTypeRef!==null) {
- return valueTypeRef;
- }
- else if(exprTypeRef!==null) {
- return exprTypeRef;
- }
- return null;
- }
-
- private def boolean isToBeConsidered(DestructNode node, Optional> astNodesToConsider) {
- return node !== null
- && (node.astElement === null || !astNodesToConsider.isPresent || astNodesToConsider.get().contains(node.astElement));
- }
-
-
- /**
- * Return the expected type of a poly expression if it is used in a destructure pattern and null otherwise.
- */
- public def TypeRef calculateExpectedType(Expression rootPoly, RuleEnvironment G, InferenceContext infCtx) {
- // In case of destructure pattern, we can calculate the expected type based on the structure of the destructure pattern.
- val rootDestructNode = if (rootPoly.eContainer instanceof VariableBinding) {
- DestructNode.unify(rootPoly.eContainer as VariableBinding)
- } else if (rootPoly.eContainer instanceof AssignmentExpression) {
- DestructNode.unify(rootPoly.eContainer as AssignmentExpression)
- } else if (rootPoly.eContainer instanceof ForStatement) {
- DestructNode.unify(rootPoly.eContainer as ForStatement)
- } else {
- null
- };
- if (rootDestructNode === null) {
- return null;
- }
- return rootDestructNode.calculateExpectedType(G, infCtx);
- }
-
- /**
- * Calculate expected type of a destructure pattern based on its structure.
- */
- private def TypeRef calculateExpectedType(DestructNode destructNode, RuleEnvironment G, InferenceContext infCtx) {
- val elementTypes = new ArrayList();
- val elementMembers = new ArrayList();
- val elemCount = destructNode.nestedNodes.size
- for (nestedNode : destructNode.nestedNodes) {
- val elemExpectedType = if (nestedNode.nestedNodes !== null && nestedNode.nestedNodes.size > 0) {
- // Recursively calculate the expected type of the nested child
- calculateExpectedType(nestedNode, G, infCtx)
- } else {
- // Extract type of leaf node
- createTypeFromLeafDestructNode(nestedNode, G);
- }
-
- if (nestedNode.propName !== null) {
- // We are dealing with object literals, hence create TStructMembers to construct a ParameterizedTypeRefStructural.
- val field = TypesFactory.eINSTANCE.createTStructField
- field.name = nestedNode.propName;
- field.typeRef = if (elemExpectedType !== null) {
- TypeUtils.copyIfContained(elemExpectedType)
- } else {
- // If the expected type is not specified, the expected type is arbitrary hence return a new inference variable.
- val iv = infCtx.newInferenceVariable;
- TypeUtils.createTypeRef(iv)
- }
- elementMembers.add(field)
- } else {
- if (elemExpectedType !== null) {
- elementTypes.add(elemExpectedType)
- } else {
- // If the expected type is not specified, the expected type is arbitrary hence return a new inference variable.
- val iv = infCtx.newInferenceVariable;
- elementTypes.add(TypeUtils.createTypeRef(iv));
- }
- }
- }
-
-
- var retTypeRef = if (elementMembers.size > 0) {
- if (elementTypes.size > 0) {
- throw new IllegalStateException("elementTypes and elementMembers can not both contain elements at the same time.")
- }
- TypeUtils.createParameterizedTypeRefStructural(G.objectType, TypingStrategy.STRUCTURAL, elementMembers)
- } else if (elementTypes.size > 0) {
- if (elemCount == 1) {
- G.arrayTypeRef(elementTypes.get(0))
- } else if (elemCount > 1){
- G.iterableNTypeRef(elemCount, elementTypes);
- } else {
- null
- }
- } else {
- null
- }
- // Wrap the expected type in an Iterable type in case of ForStatement
- // Note that we wrap the type into an Iterable type so that when a constraint G <: Iterable<...> is created,
- // we would like to reduce it to IV <:..
- if (retTypeRef !== null && DestructureUtils.isTopOfDestructuringForStatement(destructNode.astElement)) {
- retTypeRef = G.iterableTypeRef(retTypeRef)
- }
- return retTypeRef;
- }
-
- /** Create expected type for a leaf DestructNode */
- private def TypeRef createTypeFromLeafDestructNode(DestructNode leafNode, RuleEnvironment G) {
- val varDecl = leafNode.varDecl
- val varRef = leafNode.varRef
- if (varDecl !== null) {
- // If it is a variable declaration, simply retrieve the declared type
- val declTypeRef = getDeclaredTypeRefOfVarDecl(G, varDecl);
- if (declTypeRef !== null) {
- return declTypeRef;
- }
- } else if (varRef !== null) {
- // It is a variable reference, retrieve the (declared or inferred) type of the variable
- val id = varRef.id;
- if (id instanceof VariableDeclaration || id instanceof TVariable) {
- return ts.type(G, id);
- }
- }
- // In case the expected type does not exist, simply return null
- return null
- }
-
- /**
- * This is invoked from {@code PolyProcessor#inferType(RuleEnvironment, Expression, ASTMetaInfoCache)}
- * and in case of for-of loops it may inspect its variable declaration before {@code TypeRefProcessor}
- * has processed that variable declaration (see order defined for ForStatement in
- * {@link ASTProcessor#childrenToBeProcessed(RuleEnvironment, EObject)}). Therefore, we cannot rely
- * on {@link TypedElement#getDeclaredTypeRef()} here.
- */
- private def TypeRef getDeclaredTypeRefOfVarDecl(RuleEnvironment G, VariableDeclaration varDecl) {
- val typeRefNode = varDecl.declaredTypeRefNode;
- if (typeRefNode !== null) {
- val cached = typeRefNode.cachedProcessedTypeRef;
- if (cached !== null) {
- return cached;
- }
- val inAST = typeRefNode.typeRefInAST;
- if (inAST !== null) {
- return tsh.resolveTypeAliases(G, inAST);
- }
- }
- return null;
- }
-}