diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/RuntimeDependencyProcessor.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/RuntimeDependencyProcessor.java new file mode 100644 index 0000000000..a4e835cec4 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/postprocessing/RuntimeDependencyProcessor.java @@ -0,0 +1,252 @@ +/** + * Copyright (c) 2020 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.postprocessing; + +import static com.google.common.collect.Iterators.singletonIterator; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.head; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.n4js.n4JS.IdentifierRef; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.ModuleRef; +import org.eclipse.n4js.n4JS.N4ClassifierDeclaration; +import org.eclipse.n4js.n4JS.N4JSASTUtils; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.n4JS.TypeReferenceNode; +import org.eclipse.n4js.smith.Measurement; +import org.eclipse.n4js.smith.N4JSDataCollectors; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.types.IdentifiableElement; +import org.eclipse.n4js.ts.types.ModuleNamespaceVirtualType; +import org.eclipse.n4js.ts.types.RuntimeDependency; +import org.eclipse.n4js.ts.types.TExportableElement; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.ts.types.TNamespace; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.ts.types.TypesFactory; +import org.eclipse.n4js.utils.EcoreUtilN4; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.SCCUtils; +import org.eclipse.xtext.EcoreUtil2; + +import com.google.inject.Singleton; + +/** + * Processor for computing direct load-time dependencies and import retention (during AST traversal) as well as + * load-time dependency cycles (during finalization of post-processing). + */ +@Singleton +@SuppressWarnings("all") +public class RuntimeDependencyProcessor { + /** + * Invoked during AST traversal (and thus during main post-processing). + */ + void recordRuntimeReferencesInCache(final EObject node, final ASTMetaInfoCache cache) { + if (node instanceof IdentifierRef) { + IdentifierRef idRef = (IdentifierRef) node; + if (idRef.eContainingFeature() == N4JSPackage.Literals.NAMED_EXPORT_SPECIFIER__EXPORTED_ELEMENT) { + return; // re-exports are not harmful + } + IdentifiableElement target = idRef.getTargetElement(); + if (N4JSLanguageUtils.hasRuntimeRepresentation(target)) { + cache.elementsReferencedAtRuntime.add(target); + // in case of namespace imports, we also want to remember that the namespace was referenced at run time: + IdentifiableElement targetRaw = idRef.getId(); + if (targetRaw != target && targetRaw instanceof ModuleNamespaceVirtualType) { + cache.elementsReferencedAtRuntime.add(targetRaw); + } + } + } else if (node instanceof N4ClassifierDeclaration) { + N4ClassifierDeclaration cd = (N4ClassifierDeclaration) node; + if (N4JSLanguageUtils.hasRuntimeRepresentation(cd)) { + for (TypeReferenceNode targetTypeRef : cd.getSuperClassifierRefs()) { + Type targetDeclType = targetTypeRef == null || targetTypeRef.getTypeRef() == null + ? null + : targetTypeRef.getTypeRef().getDeclaredType(); + + if (N4JSLanguageUtils.hasRuntimeRepresentation(targetDeclType)) { + cache.elementsReferencedAtRuntime.add(targetDeclType); + + // in case of namespace imports, we also want to remember that the namespace was referenced at + // run time: + @SuppressWarnings("null") + Type namespaceLikeType = targetTypeRef.getTypeRefInAST() == null + || targetTypeRef.getTypeRefInAST().getAstNamespaceLikeRefs() == null + || head(targetTypeRef.getTypeRefInAST().getAstNamespaceLikeRefs()) == null + ? null + : head(targetTypeRef.getTypeRefInAST().getAstNamespaceLikeRefs()) + .getDeclaredType(); + + if (namespaceLikeType instanceof ModuleNamespaceVirtualType + || namespaceLikeType instanceof TNamespace) { + cache.elementsReferencedAtRuntime.add(namespaceLikeType); + } + // remember that the target's containing module was referenced from an + // extends/implements clause: + @SuppressWarnings("null") + TModule targetModule = (!targetDeclType.eIsProxy()) + ? EcoreUtil2.getContainerOfType(targetDeclType, TModule.class) + : null; + if (isDifferentModuleInSameProject(targetModule, cache)) { + if (N4JSASTUtils.isTopLevelCode(node)) { + // nested classifiers not supported yet, but let's be future proof + cache.modulesReferencedAtLoadtimeForInheritance.add(targetModule); + } + } + } + } + } + } + } + + /** + * Invoked at the end of AST traversal (and thus, during main post-processing). + *

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

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

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

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

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

    - *
  • references to the literal of an enum must be converted to an {@link EnumLiteralTypeRef}. - *
  • {@link TypeRef#isAliasUnresolved() unresolved} references to type aliases must be converted to - * {@link TypeRef#isAliasResolved() resolved} references to type aliases. - *
- */ -package class TypeRefProcessor extends AbstractProcessor { - - @Inject - private TypeSystemHelper tsh; - - def void handleTypeRefs(RuleEnvironment G, EObject astNode, ASTMetaInfoCache cache) { - handleTypeRefsInAST(G, astNode); - handleTypeRefsInTModule(G, astNode); - } - - def private void handleTypeRefsInAST(RuleEnvironment G, EObject astNode) { - for (TypeReferenceNode typeRefNode : N4JSASTUtils.getContainedTypeReferenceNodes(astNode)) { - val typeRef = typeRefNode.typeRefInAST; - var typeRefProcessed = processTypeArg(G, typeRef); - if (typeRefProcessed !== null) { - if (typeRefProcessed === typeRef) { - // temporary tweak to ensure correctness of code base: - // if nothing was resolved, we could directly use 'typeRef' as the value of property - // TypeReferenceNode#cachedProcessedTypeRef; however, then the return value of operation - // TypeReferenceNode#getTypeRef() would sometimes be contained in the AST and sometimes not; - // by always creating a copy here, we force the entire code base to be able to cope with the - // fact that the return value of TypeReferenceNode#getTypeRef() might not be contained in the - // AST (similarly as type aliases were used everywhere in the code, for testing) - typeRefProcessed = TypeUtils.copy(typeRef); - } - val typeRefProcessedFinal = typeRefProcessed; - EcoreUtilN4.doWithDeliver(false, [ - typeRefNode.cachedProcessedTypeRef = typeRefProcessedFinal; - ], typeRefNode); - } - } - } - - def private void handleTypeRefsInTModule(RuleEnvironment G, EObject astNode) { - val defType = switch(astNode) { - TypeDefiningElement: - astNode.definedType - StructuralTypeRef: - astNode.structuralType - FunctionTypeExpression: - astNode.declaredType - VariableDeclaration: - astNode.definedVariable - }; - if (defType !== null) { - handleTypeRefsInIdentifiableElement(G, defType); - } - } - - def private void handleTypeRefsInIdentifiableElement(RuleEnvironment G, IdentifiableElement elem) { - if (elem instanceof TypeAlias) { - return; // do not resolve the 'actualTypeRef' property in type alias itself - } - val allNestedTypeArgs = newArrayList; // create list up-front to not confuse tree iterator when replacing nodes! - val iter = elem.eAllContents; - while (iter.hasNext) { - val obj = iter.next; - if (obj instanceof TypeArgument) { - allNestedTypeArgs.add(obj); - iter.prune(); - } - } - for (typeArg : allNestedTypeArgs) { - val typeArgProcessed = processTypeArg(G, typeArg); - if (typeArgProcessed !== null && typeArgProcessed !== typeArg) { - val containmentFeature = typeArg.eContainmentFeature; - val isValidType = containmentFeature !== null - && containmentFeature.getEReferenceType().isSuperTypeOf(typeArgProcessed.eClass); - if (isValidType) { - EcoreUtilN4.doWithDeliver(false, [ - EcoreUtil.replace(typeArg, typeArgProcessed); - ], typeArg.eContainer); - } - } - } - } - - /** This overload implements the rule that when passing in a {@link TypeRef}, you get a {@code TypeRef} back. */ - def private TypeRef processTypeArg(RuleEnvironment G, TypeRef typeRef) { - return processTypeArg(G, typeRef as TypeArgument) as TypeRef; - } - - /** - * Guarantee: type references are never converted to {@link Wildcard}s, i.e. when passing in a {@link TypeRef}, - * you get a {@code TypeRef} back. - */ - def private TypeArgument processTypeArg(RuleEnvironment G, TypeArgument typeArg) { - if (typeArg === null) { - return null; - } - var processed = typeArg; - - processed = processEnumLiteralTypeRefs(G, processed); - processed = processTypeAliases(G, processed); - - return processed; - } - - def private TypeArgument processEnumLiteralTypeRefs(RuleEnvironment G, TypeArgument typeArg) { - // note: we also have to handle parameterized type refs that might be nested below some other TypeRef! - return new ResolveParameterizedTypeRefPointingToTEnumLiteralSwitch(G).doSwitch(typeArg); - } - - def private TypeArgument processTypeAliases(RuleEnvironment G, TypeArgument typeArg) { - // note: we also have to resolve type aliases that might be nested below a non-alias TypeRef! - return tsh.resolveTypeAliases(G, typeArg); - } - - - private static class ResolveParameterizedTypeRefPointingToTEnumLiteralSwitch extends NestedTypeRefsSwitch { - - new(RuleEnvironment G) { - super(G); - } - - override protected derive(RuleEnvironment G_NEW) { - return new ResolveParameterizedTypeRefPointingToTEnumLiteralSwitch(G_NEW); - } - - override protected caseParameterizedTypeRef_processDeclaredType(ParameterizedTypeRef typeRef) { - val astQualifier = typeRef.astNamespaceLikeRefs?.last?.declaredType; - if (astQualifier instanceof TEnum) { - val enumLiteralName = typeRef.declaredTypeAsText; - val enumLiteral = astQualifier.literals.findFirst[name == enumLiteralName]; - if (enumLiteral !== null) { - val litTypeRef = TypeRefsFactory.eINSTANCE.createEnumLiteralTypeRef(); - litTypeRef.value = enumLiteral; - return litTypeRef; - } - } - return typeRef; - } - } -}