diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Ordering_Helpers.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Ordering_Helpers.enso index 4cb1b8cd3222..fdc7df7195b2 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Ordering_Helpers.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Ordering_Helpers.enso @@ -37,8 +37,10 @@ type Default_Comparator ## PRIVATE hash : Number -> Integer hash x = Default_Comparator.hash_builtin x + ## PRIVATE hash_builtin x = @Builtin_Method "Default_Comparator.hash_builtin" + ## PRIVATE less_than_builtin left right = @Builtin_Method "Default_Comparator.less_than_builtin" diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta/Enso_Project.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta/Enso_Project.enso index feccbb2e7476..cc7ecfd01db9 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta/Enso_Project.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta/Enso_Project.enso @@ -96,6 +96,9 @@ type Project_Description root_path : Text root_path self = self.root.path + ## PRIVATE + enso_project_builtin module = @Builtin_Method "Project_Description.enso_project_builtin" + ## ICON enso_icon Returns the Enso project description for the project that the engine was executed with, i.e., the project that contains the `main` method, or diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso index 51decf6624fe..ca2dd010b691 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso @@ -835,6 +835,12 @@ type File to_display_text : Text to_display_text self = self.to_text + ## PRIVATE + copy_builtin self target options = @Builtin_Method "File.copy_builtin" + + ## PRIVATE + move_builtin self target options = @Builtin_Method "File.move_builtin" + ## PRIVATE Utility function that returns all descendants of the provided file, including diff --git a/engine/common/src/main/java/org/enso/common/CompilationStage.java b/engine/common/src/main/java/org/enso/common/CompilationStage.java index cf6a85083a20..8da418f39052 100644 --- a/engine/common/src/main/java/org/enso/common/CompilationStage.java +++ b/engine/common/src/main/java/org/enso/common/CompilationStage.java @@ -3,12 +3,13 @@ /** Defines a stage of compilation of the module. */ public enum CompilationStage { INITIAL(0), - AFTER_PARSING(1), - AFTER_IMPORT_RESOLUTION(2), - AFTER_GLOBAL_TYPES(3), - AFTER_STATIC_PASSES(4), - AFTER_RUNTIME_STUBS(5), - AFTER_CODEGEN(6); + AFTER_PARSING(10), + AFTER_IMPORT_RESOLUTION(20), + AFTER_GLOBAL_TYPES(30), + AFTER_STATIC_PASSES(40), + AFTER_TYPE_INFERENCE_PASSES(45), + AFTER_RUNTIME_STUBS(50), + AFTER_CODEGEN(60); private final int ordinal; diff --git a/engine/runtime-compiler/src/main/java/module-info.java b/engine/runtime-compiler/src/main/java/module-info.java index e081f014144a..e834881f7825 100644 --- a/engine/runtime-compiler/src/main/java/module-info.java +++ b/engine/runtime-compiler/src/main/java/module-info.java @@ -30,4 +30,5 @@ exports org.enso.compiler.phase; exports org.enso.compiler.phase.exports; exports org.enso.compiler.refactoring; + exports org.enso.compiler.common; } diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/MetadataInteropHelpers.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/MetadataInteropHelpers.java index 4b7f270df95d..e32134ec53e7 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/MetadataInteropHelpers.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/MetadataInteropHelpers.java @@ -34,7 +34,21 @@ public static T getMetadataOrNull(IR ir, IRProcessingPass pass, Class exp public static T getMetadata(IR ir, IRProcessingPass pass, Class expectedType) { T metadataOrNull = getMetadataOrNull(ir, pass, expectedType); if (metadataOrNull == null) { - throw new IllegalStateException("Missing expected " + pass + " metadata for " + ir + "."); + String textRepresentation = ir.toString(); + if (textRepresentation.length() > 100) { + textRepresentation = textRepresentation.substring(0, 100) + "..."; + } + + throw new IllegalStateException( + "Missing expected " + + pass + + " metadata for " + + textRepresentation + + " (" + + ir.getClass().getCanonicalName() + + "), had " + + ir.passData().toString() + + "."); } return metadataOrNull; diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/common/BuildScopeFromModuleAlgorithm.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/common/BuildScopeFromModuleAlgorithm.java new file mode 100644 index 000000000000..082c1534cba6 --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/common/BuildScopeFromModuleAlgorithm.java @@ -0,0 +1,180 @@ +package org.enso.compiler.common; + +import org.enso.compiler.MetadataInteropHelpers; +import org.enso.compiler.core.ir.Module; +import org.enso.compiler.core.ir.module.scope.Definition; +import org.enso.compiler.core.ir.module.scope.definition.Method; +import org.enso.compiler.core.ir.module.scope.imports.Polyglot; +import org.enso.compiler.data.BindingsMap; +import org.enso.compiler.pass.resolve.MethodDefinitions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import scala.jdk.javaapi.CollectionConverters; + +/** + * Gathers the common logic for building the ModuleScope. + * + *

This is done in two places: + * + *

    + *
  1. in the compiler, gathering just the types to build StaticModuleScope, + *
  2. in the runtime, building Truffle nodes for the interpreter. + *
+ * + *

The interpreter does much more than the type-checker, so currently this only gathers the + * general shape of the process to try to ensure that they stay in sync. In future iterations, we + * may try to move more of the logic to this common place. + */ +public abstract class BuildScopeFromModuleAlgorithm { + private final Logger logger = LoggerFactory.getLogger(BuildScopeFromModuleAlgorithm.class); + + protected abstract void registerExport(ImportExportScopeType exportScope); + + protected abstract void registerImport(ImportExportScopeType importScope); + + protected abstract TypeScopeReferenceType getTypeAssociatedWithCurrentScope(); + + /** Runs the main processing on a module, that will build the module scope for it. */ + public final void processModule(Module moduleIr, BindingsMap bindingsMap) { + processModuleExports(bindingsMap); + processModuleImports(bindingsMap); + processPolyglotImports(moduleIr); + + processBindings(moduleIr); + } + + private void processModuleExports(BindingsMap bindingsMap) { + for (var exportedMod : + CollectionConverters.asJavaCollection(bindingsMap.getDirectlyExportedModules())) { + ImportExportScopeType exportScope = buildExportScope(exportedMod); + registerExport(exportScope); + } + } + + private void processModuleImports(BindingsMap bindingsMap) { + for (var imp : CollectionConverters.asJavaCollection(bindingsMap.resolvedImports())) { + for (var target : CollectionConverters.asJavaCollection(imp.targets())) { + if (target instanceof BindingsMap.ResolvedModule resolvedModule) { + var importScope = buildImportScope(imp, resolvedModule); + registerImport(importScope); + } + } + } + } + + private void processPolyglotImports(Module moduleIr) { + for (var imp : CollectionConverters.asJavaCollection(moduleIr.imports())) { + if (imp instanceof Polyglot polyglotImport) { + if (polyglotImport.entity() instanceof Polyglot.Java javaEntity) { + processPolyglotJavaImport(polyglotImport.getVisibleName(), javaEntity.getJavaName()); + } else { + throw new IllegalStateException( + "Unsupported polyglot import entity: " + polyglotImport.entity()); + } + } + } + } + + private void processBindings(Module module) { + for (var binding : CollectionConverters.asJavaCollection(module.bindings())) { + switch (binding) { + case Definition.Type typ -> processTypeDefinition(typ); + case Method.Explicit method -> processMethodDefinition(method); + case Method.Conversion conversion -> processConversion(conversion); + default -> logger.warn( + "Unexpected binding type: {}", binding.getClass().getCanonicalName()); + } + } + } + + /** Allows the implementation to specify how to register polyglot Java imports. */ + protected abstract void processPolyglotJavaImport(String visibleName, String javaClassName); + + /** + * Allows the implementation to specify how to register conversions. + * + *

In the future we may want to extract some common logic from this, but for now we allow the + * implementation to specify this. + */ + protected abstract void processConversion(Method.Conversion conversion); + + /** Allows the implementation to specify how to register method definitions. */ + protected abstract void processMethodDefinition(Method.Explicit method); + + /** + * Allows the implementation to specify how to register type definitions, along with their + * constructors and getters. + * + *

The type registration (registering constructors, getters) is really complex, ideally we'd + * also like to extract some common logic from it. But the differences are very large, so setting + * that aside for later. + */ + protected abstract void processTypeDefinition(Definition.Type typ); + + /** + * Common method that allows to extract the type on which the method is defined. + * + *

    + *
  • For a member method, this will be its parent type. + *
  • For a static method, this will be the eigentype of the type on which it is defined. + *
  • For a module method, this will be the type associated with the module. + *
+ */ + protected final TypeScopeReferenceType getTypeDefiningMethod(Method.Explicit method) { + var typePointerOpt = method.methodReference().typePointer(); + if (typePointerOpt.isEmpty()) { + return getTypeAssociatedWithCurrentScope(); + } else { + var metadata = + MetadataInteropHelpers.getMetadataOrNull( + typePointerOpt.get(), MethodDefinitions.INSTANCE, BindingsMap.Resolution.class); + if (metadata == null) { + logger.debug( + "Failed to resolve type pointer for method: {}", method.methodReference().showCode()); + return null; + } + + return switch (metadata.target()) { + case BindingsMap.ResolvedType resolvedType -> associatedTypeFromResolvedType( + resolvedType, method.isStatic()); + case BindingsMap.ResolvedModule resolvedModule -> associatedTypeFromResolvedModule( + resolvedModule); + default -> throw new IllegalStateException( + "Unexpected target type: " + metadata.target().getClass().getCanonicalName()); + }; + } + } + + /** + * Implementation specific piece of {@link #getTypeDefiningMethod(Method.Explicit)} that specifies + * how to build the associated type from a resolved module. + */ + protected abstract TypeScopeReferenceType associatedTypeFromResolvedModule( + BindingsMap.ResolvedModule module); + + /** + * Implementation specific piece of {@link #getTypeDefiningMethod(Method.Explicit)} that specifies + * how to build the associated type from a resolved type, depending on if the method is static or + * not. + */ + protected abstract TypeScopeReferenceType associatedTypeFromResolvedType( + BindingsMap.ResolvedType type, boolean isStatic); + + /** + * Allows the implementation to specify how to build the export scope from an exported module + * instance. + * + *

Such scope is then registered with the scope builder using {@code addExport}. + */ + protected abstract ImportExportScopeType buildExportScope( + BindingsMap.ExportedModule exportedModule); + + /** + * Allows the implementation to specify how to build the import scope from a resolved import and + * module. + * + *

Such scope is then registered with the scope builder using {@code addImport}. + */ + protected abstract ImportExportScopeType buildImportScope( + BindingsMap.ResolvedImport resolvedImport, BindingsMap.ResolvedModule resolvedModule); +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/common/MethodResolutionAlgorithm.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/common/MethodResolutionAlgorithm.java new file mode 100644 index 000000000000..ef6d1dca861d --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/common/MethodResolutionAlgorithm.java @@ -0,0 +1,222 @@ +package org.enso.compiler.common; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * Encapsulates the logic for resolving a method call on a type/module. + * + *

The same logic is needed in two places: + * + *

    + *
  1. in the runtime ({@link + * org.enso.interpreter.runtime.scope.ModuleScope#lookupMethodDefinition}), + *
  2. in the type checker ({@link org.enso.compiler.pass.analyse.types.MethodTypeResolver}). + *
+ * + *

To ensure that all usages stay consistent, they should all rely on the logic implemented in + * this class, customizing it to the specific needs of the context in which it is used. + * + * @param the type of the module scope that the algorithm will be working with + */ +public abstract class MethodResolutionAlgorithm< + FunctionType, TypeScopeReferenceType, ImportExportScopeType, ModuleScopeType> { + + /** + * Looks up a method definition as seen in the current module. + * + *

This takes into consideration all definitions local to the module and everything that has + * been imported. + * + *

The algorithm is as follows: + * + *

    + *
  1. Methods defined in the same module as the type which is being called have the highest + * precedence. + *
  2. Next, methods defined in the current module are considered. + *
  3. Finally, methods imported from other modules. + *
+ */ + public final FunctionType lookupMethodDefinition( + ModuleScopeType currentModuleScope, TypeScopeReferenceType type, String methodName) { + var definitionScope = findDefinitionScope(type); + if (definitionScope != null) { + var definedWithAtom = getMethodFromModuleScope(definitionScope, type, methodName); + if (definedWithAtom != null) { + return definedWithAtom; + } + } + + var definedHere = getMethodFromModuleScope(currentModuleScope, type, methodName); + if (definedHere != null) { + return definedHere; + } + + return findInImports(currentModuleScope, type, methodName); + } + + /** + * Finds a method exported by a module. + * + *

It first checks methods defined in the module and later checks any methods re-exported from + * other modules. + */ + public final FunctionType getExportedMethod( + ModuleScopeType moduleScope, TypeScopeReferenceType type, String methodName) { + var definedLocally = getMethodFromModuleScope(moduleScope, type, methodName); + if (definedLocally != null) { + return definedLocally; + } + + return getExportsFromModuleScope(moduleScope).stream() + .map(scope -> getMethodForTypeFromScope(scope, type, methodName)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + /** + * Looks up a conversion definition as seen in the current module. + * + *

The algorithm is as follows: + * + *

    + *
  1. Conversions defined in the definition module of the source type are looked-up first, + *
  2. Next, conversions defined in the definition module of the target type are considered, + *
  3. Then, conversions defined in the current module are considered, + *
  4. Finally, conversions imported from other modules are considered. + *
+ */ + public final FunctionType lookupConversionDefinition( + ModuleScopeType currentModuleScope, + TypeScopeReferenceType source, + TypeScopeReferenceType target) { + var sourceDefinitionScope = findDefinitionScope(source); + var definedWithSource = getConversionFromModuleScope(sourceDefinitionScope, target, source); + if (definedWithSource != null) { + return definedWithSource; + } + + var targetDefinitionScope = findDefinitionScope(target); + var definedWithTarget = getConversionFromModuleScope(targetDefinitionScope, target, source); + if (definedWithTarget != null) { + return definedWithTarget; + } + + var definedHere = getConversionFromModuleScope(currentModuleScope, target, source); + if (definedHere != null) { + return definedHere; + } + + return getImportsFromModuleScope(currentModuleScope).stream() + .map(scope -> getExportedConversionFromScope(scope, target, source)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + /** + * Finds a conversion exported by a module. + * + *

It first checks conversions defined in the module and later checks any conversions + * re-exported from other modules. + */ + public final FunctionType getExportedConversion( + ModuleScopeType moduleScope, TypeScopeReferenceType target, TypeScopeReferenceType source) { + var definedLocally = getConversionFromModuleScope(moduleScope, target, source); + if (definedLocally != null) { + return definedLocally; + } + + return getExportsFromModuleScope(moduleScope).stream() + .map(scope -> getConversionFromScope(scope, target, source)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + private FunctionType findInImports( + ModuleScopeType currentModuleScope, TypeScopeReferenceType type, String methodName) { + var found = + getImportsFromModuleScope(currentModuleScope).stream() + .flatMap( + (importExportScope) -> { + var exportedMethod = + getExportedMethodFromScope(importExportScope, type, methodName); + if (exportedMethod != null) { + return Stream.of(new MethodFromImport<>(exportedMethod, importExportScope)); + } else { + return Stream.empty(); + } + }) + .toList(); + + if (found.size() == 1) { + return found.get(0).resolutionResult; + } else if (found.size() > 1) { + return onMultipleDefinitionsFromImports(methodName, found); + } else { + return null; + } + } + + protected abstract Collection getImportsFromModuleScope( + ModuleScopeType moduleScope); + + protected abstract Collection getExportsFromModuleScope( + ModuleScopeType moduleScope); + + protected abstract FunctionType getConversionFromModuleScope( + ModuleScopeType moduleScope, TypeScopeReferenceType target, TypeScopeReferenceType source); + + protected abstract FunctionType getMethodFromModuleScope( + ModuleScopeType moduleScope, TypeScopeReferenceType type, String methodName); + + /** Locates the module scope in which the provided type was defined. */ + protected abstract ModuleScopeType findDefinitionScope(TypeScopeReferenceType type); + + /** + * Implementation detail that should delegate to a {@code getMethodReference} variant in the given + * scope. + */ + protected abstract FunctionType getMethodForTypeFromScope( + ImportExportScopeType scope, TypeScopeReferenceType type, String methodName); + + /** + * Implementation detail that should delegate to a {@code getExportedMethod} variant in the given + * scope. + */ + protected abstract FunctionType getExportedMethodFromScope( + ImportExportScopeType scope, TypeScopeReferenceType type, String methodName); + + /** + * Implementation detail that should delegate to a {@code getConversionForType} variant in the + * given scope. + */ + protected abstract FunctionType getConversionFromScope( + ImportExportScopeType scope, TypeScopeReferenceType target, TypeScopeReferenceType source); + + /** + * Implementation detail that should delegate to a {@code getExportedConversion} variant in the + * given scope. + */ + protected abstract FunctionType getExportedConversionFromScope( + ImportExportScopeType scope, TypeScopeReferenceType target, TypeScopeReferenceType source); + + /** + * Defines the behaviour when a method resolving to distinct results is found in multiple imports. + */ + protected abstract FunctionType onMultipleDefinitionsFromImports( + String methodName, List> imports); + + /** + * Represents a method found in an import scope. + * + * @param resolutionResult the result of the resolution + * @param origin the scope in which it was found + */ + protected record MethodFromImport( + FunctionType resolutionResult, ImportExportScopeType origin) {} +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/context/NameResolutionAlgorithm.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/common/NameResolutionAlgorithm.java similarity index 92% rename from engine/runtime-compiler/src/main/java/org/enso/compiler/context/NameResolutionAlgorithm.java rename to engine/runtime-compiler/src/main/java/org/enso/compiler/common/NameResolutionAlgorithm.java index dc62bdd49758..c1d73d81f7df 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/context/NameResolutionAlgorithm.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/common/NameResolutionAlgorithm.java @@ -1,7 +1,8 @@ -package org.enso.compiler.context; +package org.enso.compiler.common; import org.enso.compiler.MetadataInteropHelpers; import org.enso.compiler.core.ConstantsNames; +import org.enso.compiler.core.IR; import org.enso.compiler.core.ir.Name.Literal; import org.enso.compiler.data.BindingsMap; import org.enso.compiler.pass.resolve.GlobalNames$; @@ -50,7 +51,7 @@ public final ResultType resolveName(Literal name, MetadataType meta) { name, GlobalNames$.MODULE$, BindingsMap.Resolution.class); if (global != null) { BindingsMap.ResolvedName resolution = global.target(); - return resolveGlobalName(resolution); + return resolveGlobalName(resolution, name); } if (name.name().equals(ConstantsNames.FROM_MEMBER)) { @@ -70,7 +71,8 @@ public final ResultType resolveName(Literal name, MetadataType meta) { protected abstract ResultType resolveLocalName(LocalNameLinkType localLink); - protected abstract ResultType resolveGlobalName(BindingsMap.ResolvedName resolvedName); + protected abstract ResultType resolveGlobalName( + BindingsMap.ResolvedName resolvedName, IR relatedIr); protected abstract ResultType resolveFromConversion(); diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PassPersistance.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PassPersistance.java index 7de99f29b66e..3a63743b5f87 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PassPersistance.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PassPersistance.java @@ -5,7 +5,9 @@ import org.enso.compiler.pass.analyse.alias.AliasMetadata; import org.enso.compiler.pass.analyse.alias.graph.Graph; import org.enso.compiler.pass.analyse.alias.graph.GraphOccurrence; -import org.enso.compiler.pass.analyse.types.TypeInference; +import org.enso.compiler.pass.analyse.types.TypeInferencePropagation; +import org.enso.compiler.pass.analyse.types.TypeInferenceSignatures; +import org.enso.compiler.pass.analyse.types.scope.StaticModuleScopeAnalysis; import org.enso.compiler.pass.resolve.DocumentationComments; import org.enso.compiler.pass.resolve.DocumentationComments$; import org.enso.compiler.pass.resolve.ExpressionAnnotations$; @@ -62,10 +64,12 @@ @Persistable(clazz = AliasMetadata.RootScope.class, id = 1262, allowInlining = false) @Persistable(clazz = AliasMetadata.ChildScope.class, id = 1263, allowInlining = false) @Persistable(clazz = Graph.Link.class, id = 1266, allowInlining = false) -@Persistable(clazz = TypeInference.class, id = 1280) -@Persistable(clazz = FramePointerAnalysis$.class, id = 1281) -@Persistable(clazz = TailCall.TailPosition.class, id = 1282) -@Persistable(clazz = CachePreferences.class, id = 1284) +@Persistable(clazz = TypeInferencePropagation.class, id = 1280) +@Persistable(clazz = TypeInferenceSignatures.class, id = 1281) +@Persistable(clazz = FramePointerAnalysis$.class, id = 1282) +@Persistable(clazz = TailCall.TailPosition.class, id = 1284) +@Persistable(clazz = CachePreferences.class, id = 1285) +@Persistable(clazz = StaticModuleScopeAnalysis.class, id = 1287) public final class PassPersistance { private PassPersistance() {} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/AtomTypeInterface.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/AtomTypeInterface.java deleted file mode 100644 index 8253650278c2..000000000000 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/AtomTypeInterface.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.enso.compiler.pass.analyse.types; - -import java.util.List; - -/** - * Describes the visible interface of an Atom Type. - * - *

This interface declares what methods can be called on instances of that type (or statically) - * and what constructors may be called on it to create new instances. - */ -interface AtomTypeInterface { - List constructors(); - - interface Constructor { - String name(); - - List arguments(); - } - - interface Argument { - String name(); - - boolean hasDefaultValue(); - - /** - * The ascribed type of the argument. - * - *

It may be {@code null} if the type is not known. - */ - TypeRepresentation getType(TypeResolver resolver); - } - - // TODO in next iteration: static and member methods -} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/AtomTypeInterfaceFromBindingsMap.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/AtomTypeInterfaceFromBindingsMap.java deleted file mode 100644 index dce35aface59..000000000000 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/AtomTypeInterfaceFromBindingsMap.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.enso.compiler.pass.analyse.types; - -import java.util.List; -import org.enso.compiler.core.ir.Expression; -import org.enso.compiler.data.BindingsMap; -import org.enso.compiler.pass.analyse.types.util.ProxyList; -import scala.jdk.javaapi.CollectionConverters$; - -/** Implementation of {@link AtomTypeInterface} that is built from a {@link BindingsMap.Type}. */ -public final class AtomTypeInterfaceFromBindingsMap implements AtomTypeInterface { - private final BindingsMap.Type type; - - public AtomTypeInterfaceFromBindingsMap(BindingsMap.Type type) { - this.type = type; - } - - // Needed for Persistable - public BindingsMap.Type type() { - return type; - } - - @Override - public List constructors() { - return new ProxyList<>( - CollectionConverters$.MODULE$.asJava(type.members()), ConstructorFromBindingsMap::new); - } - - static class ConstructorFromBindingsMap implements Constructor { - private final BindingsMap.Cons constructor; - - ConstructorFromBindingsMap(BindingsMap.Cons constructor) { - this.constructor = constructor; - } - - @Override - public String name() { - return constructor.name(); - } - - private transient List arguments = null; - - @Override - public List arguments() { - return new ProxyList<>( - CollectionConverters$.MODULE$.asJava(constructor.arguments()), - ArgumentFromBindingsMap::new); - } - } - - private static class ArgumentFromBindingsMap implements Argument { - private final BindingsMap.Argument arg; - - public ArgumentFromBindingsMap(BindingsMap.Argument arg) { - this.arg = arg; - } - - @Override - public String name() { - return arg.name(); - } - - @Override - public boolean hasDefaultValue() { - return arg.hasDefaultValue(); - } - - @Override - public TypeRepresentation getType(TypeResolver resolver) { - if (arg.typ().isEmpty()) { - return null; - } else { - Expression expression = arg.typ().get(); - return resolver.resolveTypeExpression(expression); - } - } - } -} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/BuiltinTypes.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/BuiltinTypes.java index fe3e209fcd6e..81bcb8e68c74 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/BuiltinTypes.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/BuiltinTypes.java @@ -4,31 +4,46 @@ import org.enso.pkg.QualifiedName$; /** A helper class providing the builtin types. */ -public class BuiltinTypes { - // TODO in next iterations we will want to resolve descriptions of these types based on the loaded - // std-lib (from PackageRepository, if available). Note that if the std-lib is not imported, - // some builtin types have different names - this should be handled here in some sane way. +public final class BuiltinTypes { + private BuiltinTypes() {} - public final TypeRepresentation INTEGER = fromQualifiedName("Standard.Base.Data.Numbers.Integer"); - public final TypeRepresentation FLOAT = fromQualifiedName("Standard.Base.Data.Numbers.Float"); + public static final String FQN_NUMBER = "Standard.Base.Data.Numbers.Number"; + public static final TypeRepresentation NUMBER = fromQualifiedName(FQN_NUMBER); + static final String FQN_ANY = "Standard.Base.Any.Any"; + public static final TypeRepresentation TEXT = fromQualifiedName("Standard.Base.Data.Text.Text"); + public static final TypeRepresentation BOOLEAN = + fromQualifiedName("Standard.Base.Data.Boolean.Boolean"); + public static final TypeRepresentation VECTOR = + fromQualifiedName("Standard.Base.Data.Vector.Vector"); + public static final TypeRepresentation NOTHING = + fromQualifiedName("Standard.Base.Nothing.Nothing"); - public final TypeRepresentation NUMBER = fromQualifiedName("Standard.Base.Data.Numbers.Number"); - public final TypeRepresentation TEXT = fromQualifiedName("Standard.Base.Data.Text.Text"); - public final TypeRepresentation VECTOR = fromQualifiedName("Standard.Base.Data.Vector.Vector"); - public final TypeRepresentation NOTHING = fromQualifiedName("Standard.Base.Nothing.Nothing"); - - private TypeRepresentation fromQualifiedName(String qualifiedName) { + private static TypeRepresentation fromQualifiedName(String qualifiedName) { var fqn = QualifiedName$.MODULE$.fromString(qualifiedName); - return new TypeRepresentation.AtomType(fqn, null); + return new TypeRepresentation.AtomType(fqn); } + static final String FQN_FUNCTION = "Standard.Base.Function.Function"; + private static final String FQN_INTEGER = "Standard.Base.Data.Numbers.Integer"; + public static final TypeRepresentation INTEGER = fromQualifiedName(FQN_INTEGER); + private static final String FQN_FLOAT = "Standard.Base.Data.Numbers.Float"; + public static final TypeRepresentation FLOAT = fromQualifiedName(FQN_FLOAT); + public static boolean isAny(QualifiedName qualifiedName) { var str = qualifiedName.toString(); - return str.equals("Standard.Base.Any.Any") || str.equals("Standard.Base.Any"); + return str.equals(FQN_ANY); } public static boolean isFunction(QualifiedName qualifiedName) { var str = qualifiedName.toString(); - return str.equals("Standard.Base.Function.Function"); + return str.equals(FQN_FUNCTION); + } + + public static boolean isInteger(QualifiedName qualifiedName) { + return qualifiedName.toString().equals(FQN_INTEGER); + } + + public static boolean isFloat(QualifiedName qualifiedName) { + return qualifiedName.toString().equals(FQN_FLOAT); } } diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/CommonTypeHelpers.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/CommonTypeHelpers.java deleted file mode 100644 index f7ae57e6eae4..000000000000 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/CommonTypeHelpers.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.enso.compiler.pass.analyse.types; - -import org.enso.compiler.core.ir.Expression; -import org.enso.compiler.core.ir.ProcessingPass; -import scala.Option; - -class CommonTypeHelpers { - static TypeRepresentation getInferredType(Expression expression) { - Option r = expression.passData().get(TypeInference.INSTANCE); - if (r.isDefined()) { - InferredType metadata = (InferredType) r.get(); - return metadata.type(); - } else { - return null; - } - } -} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/InferredType.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/InferredType.java index 1a577e4a7cdb..d58f68c7cb3c 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/InferredType.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/InferredType.java @@ -5,10 +5,10 @@ import scala.Option; /** - * The metadata information associated with the {@link TypeInference} pass. + * The metadata information associated with the {@link TypeInferencePropagation} pass. * * @param type the type inferred for a given expression - * @see TypeInference + * @see TypeInferencePropagation */ public record InferredType(TypeRepresentation type) implements ProcessingPass.Metadata { @Override diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/MethodTypeResolver.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/MethodTypeResolver.java new file mode 100644 index 000000000000..6d862cc60adf --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/MethodTypeResolver.java @@ -0,0 +1,34 @@ +package org.enso.compiler.pass.analyse.types; + +import org.enso.compiler.pass.analyse.types.scope.ModuleResolver; +import org.enso.compiler.pass.analyse.types.scope.StaticMethodResolution; +import org.enso.compiler.pass.analyse.types.scope.StaticModuleScope; +import org.enso.compiler.pass.analyse.types.scope.TypeHierarchy; +import org.enso.compiler.pass.analyse.types.scope.TypeScopeReference; + +/** A helper that deals with resolving types of method calls. */ +class MethodTypeResolver { + private final StaticModuleScope currentModuleScope; + private final StaticMethodResolution methodResolutionAlgorithm; + + MethodTypeResolver(ModuleResolver moduleResolver, StaticModuleScope currentModuleScope) { + this.currentModuleScope = currentModuleScope; + this.methodResolutionAlgorithm = new StaticMethodResolution(moduleResolver); + } + + TypeRepresentation resolveMethod(TypeScopeReference type, String methodName) { + var definition = + methodResolutionAlgorithm.lookupMethodDefinition(currentModuleScope, type, methodName); + if (definition != null) { + return definition; + } + + // If not found in current scope, try parents + var parent = TypeHierarchy.getParent(type); + if (parent == null) { + return null; + } + + return resolveMethod(parent, methodName); + } +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeCompatibility.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeCompatibility.java index f581df86f287..d1cdc79adbf6 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeCompatibility.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeCompatibility.java @@ -2,9 +2,7 @@ /** A class that helps with computing compatibility between types. */ class TypeCompatibility { - TypeCompatibility(BuiltinTypes builtinTypes) { - this.builtinTypes = builtinTypes; - } + TypeCompatibility() {} /** Denotes if a given provided type can fit into an expected type. */ enum Compatibility { @@ -31,8 +29,6 @@ enum Compatibility { UNKNOWN; } - private final BuiltinTypes builtinTypes; - Compatibility computeTypeCompatibility(TypeRepresentation expected, TypeRepresentation provided) { // Exact type match is always OK. if (expected.equals(provided)) { @@ -50,8 +46,8 @@ Compatibility computeTypeCompatibility(TypeRepresentation expected, TypeRepresen return Compatibility.UNKNOWN; } - if (expected.equals(builtinTypes.NUMBER)) { - if (provided.equals(builtinTypes.INTEGER) || provided.equals(builtinTypes.FLOAT)) { + if (expected.equals(BuiltinTypes.NUMBER)) { + if (provided.equals(BuiltinTypes.INTEGER) || provided.equals(BuiltinTypes.FLOAT)) { return Compatibility.ALWAYS_COMPATIBLE; } } diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeInferencePersistance.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeInferencePersistance.java index 32b936922f78..0bb25c793f1c 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeInferencePersistance.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeInferencePersistance.java @@ -11,7 +11,6 @@ @Persistable(clazz = TypeRepresentation.IntersectionType.class, id = 34005) @Persistable(clazz = TypeRepresentation.SumType.class, id = 34006) @Persistable(clazz = TypeRepresentation.UnresolvedSymbol.class, id = 34007) -@Persistable(clazz = AtomTypeInterfaceFromBindingsMap.class, id = 34010) @Persistable(clazz = QualifiedName.class, id = 34012) public final class TypeInferencePersistance { private TypeInferencePersistance() {} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeInference.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeInferencePropagation.java similarity index 61% rename from engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeInference.java rename to engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeInferencePropagation.java index ded8086c8862..9a52d3700873 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeInference.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeInferencePropagation.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Objects; +import org.enso.compiler.PackageRepository; import org.enso.compiler.context.InlineContext; import org.enso.compiler.context.ModuleContext; import org.enso.compiler.core.IR; @@ -13,16 +14,18 @@ import org.enso.compiler.pass.IRPass; import org.enso.compiler.pass.IRProcessingPass; import org.enso.compiler.pass.analyse.BindingAnalysis$; +import org.enso.compiler.pass.analyse.types.scope.ModuleResolver; +import org.enso.compiler.pass.analyse.types.scope.StaticModuleScopeAnalysis; import org.enso.compiler.pass.resolve.FullyQualifiedNames$; import org.enso.compiler.pass.resolve.GlobalNames$; import org.enso.compiler.pass.resolve.Patterns$; import org.enso.compiler.pass.resolve.TypeNames$; import org.enso.compiler.pass.resolve.TypeSignatures$; +import org.enso.scala.wrapper.ScalaConversions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import scala.Option; import scala.collection.immutable.Seq; -import scala.collection.immutable.Seq$; -import scala.jdk.javaapi.CollectionConverters; /** * The compiler pass implementing the proof of concept of type inference. @@ -66,32 +69,66 @@ * * @see TypePropagation for more details on the type propagation mechanism */ -public final class TypeInference implements IRPass { - public static final TypeInference INSTANCE = new TypeInference(); - private static final Logger logger = LoggerFactory.getLogger(TypeInference.class); - private final BuiltinTypes builtinTypes = new BuiltinTypes(); +public final class TypeInferencePropagation implements IRPass { + public static final TypeInferencePropagation INSTANCE = new TypeInferencePropagation(); + private static final Logger LOGGER = LoggerFactory.getLogger(TypeInferencePropagation.class); private final TypeResolver typeResolver = new TypeResolver(); - private final TypeCompatibility checker = new TypeCompatibility(builtinTypes); - private final TypePropagation typePropagation = - new TypePropagation(typeResolver, checker, builtinTypes) { - @Override - protected void encounteredIncompatibleTypes( - IR relatedIr, TypeRepresentation expected, TypeRepresentation provided) { - relatedIr - .getDiagnostics() - .add( - new Warning.TypeMismatch( - relatedIr.identifiedLocation(), expected.toString(), provided.toString())); - } - - @Override - protected void encounteredInvocationOfNonFunctionType( - IR relatedIr, TypeRepresentation type) { - relatedIr - .getDiagnostics() - .add(new Warning.NotInvokable(relatedIr.identifiedLocation(), type.toString())); - } - }; + private final TypeCompatibility checker = new TypeCompatibility(); + + private TypeInferencePropagation() {} + + private TypePropagation propagationResolverInModule( + Module module, Option packageRepository) { + var packageRepo = packageRepository.isDefined() ? packageRepository.get() : null; + ModuleResolver moduleResolver = new ModuleResolver(packageRepo); + return new TypePropagation(typeResolver, checker, module, moduleResolver) { + @Override + protected void encounteredIncompatibleTypes( + IR relatedIr, TypeRepresentation expected, TypeRepresentation provided) { + relatedIr + .getDiagnostics() + .add( + new Warning.TypeMismatch( + relatedIr.identifiedLocation(), expected.toString(), provided.toString())); + } + + @Override + protected void encounteredInvocationOfNonFunctionType(IR relatedIr, TypeRepresentation type) { + relatedIr + .getDiagnostics() + .add(new Warning.NotInvokable(relatedIr.identifiedLocation(), type.toString())); + } + + @Override + protected void encounteredNoSuchMethod( + IR relatedIr, TypeRepresentation type, String methodName, MethodCallKind kind) { + String methodDescription = + switch (kind) { + case MEMBER -> "member method `" + methodName + "` on type " + type; + case STATIC -> "static method `" + methodName + "` on " + type; + case MODULE -> "method `" + methodName + "` on module " + type; + }; + relatedIr + .getDiagnostics() + .add(new Warning.NoSuchMethod(relatedIr.identifiedLocation(), methodDescription)); + } + + @Override + protected void encounteredNoSuchConstructor( + IR relatedIr, TypeRepresentation type, String constructorName) { + // TODO make sure if NoSuchMethod is right or we need a separate type here + String methodDescription = "constructor `" + constructorName + "` on type " + type; + relatedIr + .getDiagnostics() + .add(new Warning.NoSuchMethod(relatedIr.identifiedLocation(), methodDescription)); + } + }; + } + + @Override + public String toString() { + return "TypeInferencePropagation"; + } @Override public Seq precursorPasses() { @@ -102,20 +139,22 @@ public Seq precursorPasses() { FullyQualifiedNames$.MODULE$, TypeNames$.MODULE$, Patterns$.MODULE$, - TypeSignatures$.MODULE$); - return CollectionConverters.asScala(passes).toList(); + TypeSignatures$.MODULE$, + StaticModuleScopeAnalysis.INSTANCE, + TypeInferenceSignatures.INSTANCE); + return ScalaConversions.seq(passes); } @Override - @SuppressWarnings("unchecked") public Seq invalidatedPasses() { - return (Seq) Seq$.MODULE$.empty(); + return ScalaConversions.nil(); } @Override public Module runModule(Module ir, ModuleContext moduleContext) { + TypePropagation typePropagation = propagationResolverInModule(ir, moduleContext.pkgRepo()); ir.bindings() - .map( + .foreach( (def) -> switch (def) { case Method.Explicit b -> { @@ -130,16 +169,17 @@ public Module runModule(Module ir, ModuleContext moduleContext) { } case Definition.Type typ -> typ; default -> { - logger.trace("UNEXPECTED definition {}", def.getClass().getCanonicalName()); + LOGGER.trace("UNEXPECTED definition {}", def.getClass().getCanonicalName()); yield def; } }); - return ir; } @Override public Expression runExpression(Expression ir, InlineContext inlineContext) { + var typePropagation = + propagationResolverInModule(inlineContext.getModule().getIr(), inlineContext.pkgRepo()); TypeRepresentation inferredType = typePropagation.tryInferringType(ir, LocalBindingsTyping.create()); if (inferredType != null) { diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeInferenceSignatures.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeInferenceSignatures.java new file mode 100644 index 000000000000..48b169cce94a --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeInferenceSignatures.java @@ -0,0 +1,196 @@ +package org.enso.compiler.pass.analyse.types; + +import java.util.List; +import org.enso.compiler.context.InlineContext; +import org.enso.compiler.context.ModuleContext; +import org.enso.compiler.core.ir.Expression; +import org.enso.compiler.core.ir.Function; +import org.enso.compiler.core.ir.Module; +import org.enso.compiler.core.ir.Name; +import org.enso.compiler.core.ir.expression.Application; +import org.enso.compiler.core.ir.module.scope.Definition; +import org.enso.compiler.core.ir.module.scope.definition.Method; +import org.enso.compiler.pass.IRPass; +import org.enso.compiler.pass.IRProcessingPass; +import org.enso.compiler.pass.analyse.BindingAnalysis$; +import org.enso.compiler.pass.analyse.types.scope.StaticModuleScopeAnalysis; +import org.enso.compiler.pass.resolve.FullyQualifiedNames$; +import org.enso.compiler.pass.resolve.GlobalNames$; +import org.enso.compiler.pass.resolve.Patterns$; +import org.enso.compiler.pass.resolve.TypeNames$; +import org.enso.compiler.pass.resolve.TypeSignatures$; +import org.enso.scala.wrapper.ScalaConversions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import scala.collection.immutable.Seq; + +// TODO it may make sense to merge this pass into StaticModuleScopeAnalysis, there is little benefit +// to keeping it separate + +/** + * A precursor pass that prepares the IR for type inference, run before the main propagation logic + * runs in {@link TypeInferencePropagation}. + * + *

It handles storing inferred types based on signatures of the top-level bindings. This is done + * as a separate pass, to ensure that once propagation runs, all top-level bindings that have a + * signature, already have an inferred type assigned to them, so that these types can be used. This + * makes the job of the propagation pass much easier, avoiding it to deal with ensuring in what + * order the types are inferred, and ensuring that even without a more complicated unification logic + * that will be needed for recursive definitions, many types can already be inferred. In the future, + * it may be possible that this pass will no longer be needed - if the propagation pass will be + * smart enough to deal with the unknowns. But for now it gives us a very big gain very quickly - as + * most of our standard library is annotated with type signatures, we will be able to benefit from + * these right away, without needing to implement more complicated recursive inference logic. + * + *

This pass is very simple - it looks at ascribed types of the top level bindings to find out + * expected types of each function's arguments, and looks at the outer-most expression of the + * binding in search for a return-type ascription (these ascriptions are inserted by the return-type + * ascription translation in {@code addTypeAscription} within {@link + * org.enso.compiler.core.TreeToIr}). It does not look any deeper into the expressions, ensuring + * that it is relatively quick to run. + */ +public final class TypeInferenceSignatures implements IRPass { + private TypeInferenceSignatures() {} + + public static final TypeInferenceSignatures INSTANCE = new TypeInferenceSignatures(); + private static final Logger logger = LoggerFactory.getLogger(TypeInferenceSignatures.class); + private final TypeResolver typeResolver = new TypeResolver(); + + @Override + public String toString() { + return "TypeInferenceSignatures"; + } + + @Override + public Seq precursorPasses() { + List passes = + List.of( + BindingAnalysis$.MODULE$, + GlobalNames$.MODULE$, + FullyQualifiedNames$.MODULE$, + TypeNames$.MODULE$, + Patterns$.MODULE$, + TypeSignatures$.MODULE$); + return ScalaConversions.seq(passes); + } + + @Override + public Seq invalidatedPasses() { + List passes = + List.of(TypeInferencePropagation.INSTANCE, StaticModuleScopeAnalysis.INSTANCE); + return ScalaConversions.seq(passes); + } + + @Override + public Module runModule(Module ir, ModuleContext moduleContext) { + ir.bindings() + .foreach( + (def) -> + switch (def) { + case Method.Explicit b -> { + boolean keepSelfArgument = b.isStaticWrapperForInstanceMethod(); + TypeRepresentation resolvedType = + resolveTopLevelTypeSignature(b.body(), keepSelfArgument); + if (resolvedType != null) { + b.passData().update(INSTANCE, new InferredType(resolvedType)); + } + yield b; + } + case Definition.Type typ -> typ; + default -> { + logger.trace("UNEXPECTED definition {}", def.getClass().getCanonicalName()); + yield def; + } + }); + return ir; + } + + @Override + public Expression runExpression(Expression ir, InlineContext inlineContext) { + // This pass does not do anything when run on expressions. It only processes top-level bindings. + return ir; + } + + /** + * Constructs the type signature for a given method body. + * + * @param body the method body + * @param keepSelfArgument whether to keep the self argument. For regular static methods, the self + * argument is synthetic and does not take part in type inference. For member methods, it is + * provided implicitly when resolving the call, so again it is ignored for type inference. It + * should only be kept for such static methods that are wrappers for instance methods. + */ + private TypeRepresentation resolveTopLevelTypeSignature( + Expression body, boolean keepSelfArgument) { + return switch (body) { + // Combine argument types with ascribed type (if available) for a function type signature + case Function.Lambda lambda -> { + boolean hasAnyDefaults = + lambda.arguments().find((arg) -> arg.defaultValue().isDefined()).isDefined(); + if (hasAnyDefaults) { + // TODO inferring types that have default arguments + yield null; + } + + scala.collection.immutable.List argTypesScala = + lambda + .arguments() + // Filter out the self argument, unless it should be kept. + .filter( + (arg) -> { + if (arg.name() instanceof Name.Self selfArg) { + if (selfArg.synthetic()) { + // The 'synthetic' self is always dropped. + return false; + } else { + return keepSelfArgument; + } + } else { + // We keep all other args. + return true; + } + }) + .map( + (arg) -> { + if (arg.ascribedType().isDefined()) { + Expression typeExpression = arg.ascribedType().get(); + var resolvedTyp = typeResolver.resolveTypeExpression(typeExpression); + if (resolvedTyp != null) { + return resolvedTyp; + } + } + + return TypeRepresentation.UNKNOWN; + }); + + TypeRepresentation ascribedReturnType = findReturnTypeAscription(lambda.body()); + + if (ascribedReturnType == null && argTypesScala.isEmpty()) { + // If we did not infer return type NOR arity, we know nothing useful about this function, + // so we withdraw. + yield null; + } + + TypeRepresentation returnType = + ascribedReturnType != null ? ascribedReturnType : TypeRepresentation.UNKNOWN; + yield TypeRepresentation.buildFunction(ScalaConversions.asJava(argTypesScala), returnType); + } + + // Otherwise, we encountered a 0-argument method, so its type is just its return type (if + // its known). + default -> findReturnTypeAscription(body); + }; + } + + /** Finds the type ascription associated with the function's return type. */ + private TypeRepresentation findReturnTypeAscription(Expression expression) { + // Special handling for the FORCE node - it wraps the original return value and thus the type + // ascription that was on the outermost expression is no longer outermost - it is now inside of + // the force node, so we need to go 1 level deeper and inspect the target. + if (expression instanceof Application.Force force) { + return typeResolver.getTypeAscription(force.target()); + } + + return typeResolver.getTypeAscription(expression); + } +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypePropagation.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypePropagation.java index 27f254c31679..d8da26588df7 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypePropagation.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypePropagation.java @@ -1,17 +1,17 @@ package org.enso.compiler.pass.analyse.types; import static org.enso.compiler.MetadataInteropHelpers.getMetadata; -import static org.enso.compiler.MetadataInteropHelpers.getMetadataOrNull; import java.util.List; import org.enso.compiler.MetadataInteropHelpers; -import org.enso.compiler.context.NameResolutionAlgorithm; +import org.enso.compiler.common.NameResolutionAlgorithm; import org.enso.compiler.core.CompilerError; import org.enso.compiler.core.IR; import org.enso.compiler.core.ir.CallArgument; import org.enso.compiler.core.ir.Expression; import org.enso.compiler.core.ir.Function; import org.enso.compiler.core.ir.Literal; +import org.enso.compiler.core.ir.Module; import org.enso.compiler.core.ir.Name; import org.enso.compiler.core.ir.Pattern; import org.enso.compiler.core.ir.expression.Application; @@ -21,12 +21,15 @@ import org.enso.compiler.pass.analyse.alias.AliasMetadata; import org.enso.compiler.pass.analyse.alias.graph.Graph; import org.enso.compiler.pass.analyse.alias.graph.GraphOccurrence; -import org.enso.compiler.pass.resolve.TypeSignatures; -import org.enso.compiler.pass.resolve.TypeSignatures$; +import org.enso.compiler.pass.analyse.types.scope.AtomTypeDefinition; +import org.enso.compiler.pass.analyse.types.scope.ModuleResolver; +import org.enso.compiler.pass.analyse.types.scope.StaticModuleScope; +import org.enso.compiler.pass.analyse.types.scope.TypeScopeReference; +import org.enso.pkg.QualifiedName; +import org.enso.scala.wrapper.ScalaConversions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import scala.Option; -import scala.jdk.javaapi.CollectionConverters$; /** * A helper class providing the logic of propagating types through the IR. @@ -40,15 +43,20 @@ abstract class TypePropagation { private static final Logger logger = LoggerFactory.getLogger(TypePropagation.class); private final TypeResolver typeResolver; private final TypeCompatibility compatibilityChecker; - private final BuiltinTypes builtinTypes; + private final ModuleResolver moduleResolver; + private final MethodTypeResolver methodTypeResolver; TypePropagation( TypeResolver typeResolver, TypeCompatibility compatibilityChecker, - BuiltinTypes builtinTypes) { + Module currentModule, + ModuleResolver moduleResolver) { this.typeResolver = typeResolver; this.compatibilityChecker = compatibilityChecker; - this.builtinTypes = builtinTypes; + this.moduleResolver = moduleResolver; + + var currentModuleScope = StaticModuleScope.forIR(currentModule); + this.methodTypeResolver = new MethodTypeResolver(moduleResolver, currentModuleScope); } /** @@ -70,6 +78,26 @@ protected abstract void encounteredIncompatibleTypes( protected abstract void encounteredInvocationOfNonFunctionType( IR relatedIr, TypeRepresentation type); + /** + * The callback that is called when a method is being invoked on a type that does not have such a + * method. + */ + protected abstract void encounteredNoSuchMethod( + IR relatedIr, TypeRepresentation type, String methodName, MethodCallKind kind); + + /** + * The callback that is called when a constructor is being invoked on a type that does not have + * such a constructor. + */ + protected abstract void encounteredNoSuchConstructor( + IR relatedIr, TypeRepresentation type, String constructorName); + + enum MethodCallKind { + MEMBER, + STATIC, + MODULE + } + void checkTypeCompatibility( IR relatedIr, TypeRepresentation expected, TypeRepresentation provided) { TypeCompatibility.Compatibility compatibility = @@ -107,7 +135,7 @@ TypeRepresentation tryInferringType( var bindingType = tryInferringType(b.expression(), localBindingsTyping); if (bindingType != null) { registerBinding(b, bindingType, localBindingsTyping); - TypeInference.setInferredType(b, bindingType); + TypeInferencePropagation.setInferredType(b, bindingType); } yield bindingType; } @@ -119,7 +147,7 @@ TypeRepresentation tryInferringType( } case Function.Lambda f -> processLambda(f, localBindingsTyping); case Literal l -> processLiteral(l); - case Application.Sequence sequence -> builtinTypes.VECTOR; + case Application.Sequence sequence -> BuiltinTypes.VECTOR; case Case.Expr caseExpr -> processCaseExpression(caseExpr, localBindingsTyping); default -> { logger.trace( @@ -128,7 +156,7 @@ TypeRepresentation tryInferringType( } }; - TypeRepresentation ascribedType = findTypeAscription(expression); + TypeRepresentation ascribedType = typeResolver.getTypeAscription(expression); checkInferredAndAscribedTypeCompatibility(expression, inferredType, ascribedType); // We now override the inferred type on the expression, preferring the ascribed type if it is @@ -139,7 +167,7 @@ TypeRepresentation tryInferringType( private TypeRepresentation processCaseExpression( Case.Expr caseExpr, LocalBindingsTyping localBindingsTyping) { List innerTypes = - CollectionConverters$.MODULE$.asJava(caseExpr.branches()).stream() + ScalaConversions.asJava(caseExpr.branches()).stream() .map( branch -> { // Fork the bindings map for each branch, as in the future type equality @@ -169,9 +197,9 @@ private TypeRepresentation processName( private TypeRepresentation processLiteral(Literal literal) { return switch (literal) { case Literal.Number number -> number.isFractional() - ? builtinTypes.FLOAT - : builtinTypes.INTEGER; - case Literal.Text text -> builtinTypes.TEXT; + ? BuiltinTypes.FLOAT + : BuiltinTypes.INTEGER; + case Literal.Text text -> BuiltinTypes.TEXT; // This branch is needed only because Java is unable to infer that the match is exhaustive default -> throw new IllegalStateException( "Impossible - unknown literal type: " + literal.getClass().getCanonicalName()); @@ -230,8 +258,7 @@ private TypeRepresentation processLambda( returnType = TypeRepresentation.UNKNOWN; } - return TypeRepresentation.buildFunction( - CollectionConverters$.MODULE$.asJava(argTypesScala), returnType); + return TypeRepresentation.buildFunction(ScalaConversions.asJava(argTypesScala), returnType); } @SuppressWarnings("unchecked") @@ -286,7 +313,7 @@ private TypeRepresentation processSingleApplication( case TypeRepresentation.UnresolvedSymbol unresolvedSymbol -> { return processUnresolvedSymbolApplication( - unresolvedSymbol, argument.value(), localBindingsTyping); + unresolvedSymbol, argument.value(), localBindingsTyping, relatedIR); } default -> { @@ -306,37 +333,129 @@ private TypeRepresentation processSingleApplication( return null; } + private AtomTypeDefinition findTypeDefinition(QualifiedName name) { + var module = moduleResolver.findContainingModule(TypeScopeReference.atomType(name)); + var moduleScope = StaticModuleScope.forIR(module); + return moduleScope.getType(name.item()); + } + private TypeRepresentation processUnresolvedSymbolApplication( TypeRepresentation.UnresolvedSymbol function, Expression argument, - LocalBindingsTyping localBindingsTyping) { + LocalBindingsTyping localBindingsTyping, + IR relatedWholeApplicationIR) { var argumentType = tryInferringType(argument, localBindingsTyping); if (argumentType == null) { - return null; + argumentType = TypeRepresentation.ANY; } switch (argumentType) { case TypeRepresentation.TypeObject typeObject -> { - var ctorCandidate = - typeObject.typeInterface().constructors().stream() - .filter(ctor -> ctor.name().equals(function.name())) - .findFirst(); - if (ctorCandidate.isPresent()) { - return typeResolver.buildAtomConstructorType(typeObject, ctorCandidate.get()); + if (isConstructorOrType(function.name())) { + return resolveConstructorOnType(typeObject, function.name(), relatedWholeApplicationIR); } else { - // TODO if no ctor found, we should search static methods, but that is not implemented - // currently; so we cannot report an error either - just do nothing + // We resolve static calls on the eigen type. It should also contain registrations of the + // static variants of member methods, so we don't need to inspect member scope. + var staticScope = TypeScopeReference.atomEigenType(typeObject.name()); + var resolvedStaticMethod = methodTypeResolver.resolveMethod(staticScope, function.name()); + if (resolvedStaticMethod == null) { + encounteredNoSuchMethod( + relatedWholeApplicationIR, argumentType, function.name(), MethodCallKind.STATIC); + } + return resolvedStaticMethod; + } + } + + case TypeRepresentation.ModuleReference moduleReference -> { + var typeScope = TypeScopeReference.moduleAssociatedType(moduleReference.name()); + + if (isConstructorOrType(function.name())) { + // This is a special case when we are accessing a type inside a module, e.g. Mod.Type + // 'call' should resolve to the type + // TODO for later return null; + } else { + var resolvedModuleMethod = methodTypeResolver.resolveMethod(typeScope, function.name()); + if (resolvedModuleMethod == null) { + encounteredNoSuchMethod( + relatedWholeApplicationIR, argumentType, function.name(), MethodCallKind.MODULE); + } + return resolvedModuleMethod; + } + } + + case TypeRepresentation.AtomType atomInstanceType -> { + var typeScope = TypeScopeReference.atomType(atomInstanceType.fqn()); + var resolvedMemberMethod = methodTypeResolver.resolveMethod(typeScope, function.name()); + if (resolvedMemberMethod == null) { + encounteredNoSuchMethod( + relatedWholeApplicationIR, argumentType, function.name(), MethodCallKind.MEMBER); + } + return resolvedMemberMethod; + } + + case TypeRepresentation.TopType topType -> { + // We don't report not found methods here, because the top type can be anything, so the call + // 'may' be valid and we only want to report guaranteed failures + return methodTypeResolver.resolveMethod(TypeScopeReference.ANY, function.name()); + } + + // This is not calling this function, instead it is calling the _method_ represented by the + // UnresolvedSymbol on this Function object. + case TypeRepresentation.ArrowType functionAsObject -> { + var typeScope = TypeScopeReference.atomType(functionAsObject.getAssociatedType()); + var resolvedMethod = methodTypeResolver.resolveMethod(typeScope, function.name()); + if (resolvedMethod == null) { + encounteredNoSuchMethod( + relatedWholeApplicationIR, functionAsObject, function.name(), MethodCallKind.MEMBER); } + return resolvedMethod; } default -> { + // TODO calling on sum types, intersection types, etc. + return null; + } + } + } + + private boolean isConstructorOrType(String name) { + assert !name.isEmpty(); + char firstCharacter = name.charAt(0); + return Character.isUpperCase(firstCharacter); + } + + private TypeRepresentation resolveConstructorOnType( + TypeRepresentation.TypeObject typeObject, + String constructorName, + IR relatedWholeApplicationIR) { + assert isConstructorOrType(constructorName); + var typeDefinition = findTypeDefinition(typeObject.name()); + if (typeDefinition == null) { + logger.warn( + "resolveConstructorOnType: {} - no type definition found for {}", + relatedWholeApplicationIR.showCode(), + typeObject.name()); + return null; + } + + var constructor = typeDefinition.getConstructor(constructorName); + if (constructor != null) { + if (constructor.type() == null) { + // type is unknown due to default arguments + // TODO later on this should be assert != null because all constructors should have a + // type (once we can deal with default arguments) return null; } + + return constructor.type(); + } else { + encounteredNoSuchConstructor(relatedWholeApplicationIR, typeObject, constructorName); + return null; } } - private class CompilerNameResolution + private final class CompilerNameResolution extends NameResolutionAlgorithm< TypeRepresentation, CompilerNameResolution.LinkInfo, AliasMetadata.Occurrence> { private final LocalBindingsTyping localBindingsTyping; @@ -359,22 +478,23 @@ protected TypeRepresentation resolveLocalName(LinkInfo localLink) { } @Override - protected TypeRepresentation resolveGlobalName(BindingsMap.ResolvedName resolvedName) { + protected TypeRepresentation resolveGlobalName( + BindingsMap.ResolvedName resolvedName, IR relatedIr) { return switch (resolvedName) { - // TODO investigate when do these appear?? I did not yet see them in the wild case BindingsMap.ResolvedConstructor ctor -> { - var constructorInterface = - new AtomTypeInterfaceFromBindingsMap.ConstructorFromBindingsMap(ctor.cons()); - yield typeResolver.buildAtomConstructorType( - typeResolver.resolvedTypeAsTypeObject(ctor.tpe()), constructorInterface); + var parentType = new TypeRepresentation.TypeObject(ctor.tpe().qualifiedName()); + yield resolveConstructorOnType(parentType, ctor.cons().name(), relatedIr); } case BindingsMap.ResolvedType tpe -> typeResolver.resolvedTypeAsTypeObject(tpe); + case BindingsMap.ResolvedModule mod -> new TypeRepresentation.ModuleReference( + mod.qualifiedName()); + default -> { logger.trace( - "processGlobalName: global scope reference to {} - currently global inference is" - + " unsupported", + "resolveGlobalName: reference to {} - is currently not being resolved in static" + + " analysis", resolvedName); yield null; } @@ -430,7 +550,7 @@ private void registerPattern(Pattern pattern, LocalBindingsTyping localBindingsT registerBinding(typePattern.name(), type, localBindingsTyping); } case Pattern.Constructor constructorPattern -> { - for (var innerPattern : CollectionConverters$.MODULE$.asJava(constructorPattern.fields())) { + for (var innerPattern : ScalaConversions.asJava(constructorPattern.fields())) { registerPattern(innerPattern, localBindingsTyping); } } @@ -438,16 +558,6 @@ private void registerPattern(Pattern pattern, LocalBindingsTyping localBindingsT } } - private TypeRepresentation findTypeAscription(Expression ir) { - TypeSignatures.Signature ascribedSignature = - getMetadataOrNull(ir, TypeSignatures$.MODULE$, TypeSignatures.Signature.class); - if (ascribedSignature != null) { - return typeResolver.resolveTypeExpression(ascribedSignature.signature()); - } else { - return null; - } - } - private void checkInferredAndAscribedTypeCompatibility( Expression ir, TypeRepresentation inferredType, TypeRepresentation ascribedType) { if (ascribedType != null && inferredType != null) { diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeRepresentation.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeRepresentation.java index 46fb63375657..86b7e6e40be3 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeRepresentation.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeRepresentation.java @@ -11,11 +11,12 @@ public sealed interface TypeRepresentation permits TypeRepresentation.ArrowType, TypeRepresentation.AtomType, TypeRepresentation.IntersectionType, + TypeRepresentation.ModuleReference, TypeRepresentation.SumType, TypeRepresentation.TopType, TypeRepresentation.TypeObject, TypeRepresentation.UnresolvedSymbol { - TypeRepresentation ANY = new TopType(); + TopType ANY = new TopType(); // In the future we may want to split this unknown type to be a separate entity. TypeRepresentation UNKNOWN = ANY; @@ -38,6 +39,10 @@ record TopType() implements TypeRepresentation { public String toString() { return "Any"; } + + public QualifiedName getAssociatedType() { + return QualifiedName.fromString(BuiltinTypes.FQN_ANY); + } } /** @@ -46,8 +51,7 @@ public String toString() { *

Instances that are assigned this type are built with one of the available constructors, but * statically we do not necessarily know which one. */ - record AtomType(QualifiedName fqn, AtomTypeInterface typeInterface) - implements TypeRepresentation { + record AtomType(QualifiedName fqn) implements TypeRepresentation { @Override public String toString() { return fqn.item(); @@ -72,7 +76,22 @@ record ArrowType(TypeRepresentation argType, TypeRepresentation resultType) implements TypeRepresentation { @Override public String toString() { - return "(" + argType + " -> " + resultType + ")"; + String arg = argType.toString(); + String res = resultType.toString(); + + // If the inner type is complex (e.g. nested function), wrap it in parentheses. + if (arg.contains(" ")) { + arg = "(" + arg + ")"; + } + if (res.contains(" ")) { + res = "(" + res + ")"; + } + + return arg + " -> " + res; + } + + public QualifiedName getAssociatedType() { + return QualifiedName.fromString(BuiltinTypes.FQN_FUNCTION); } } @@ -137,10 +156,8 @@ public String toString() { * using its constructors, which will be assigned the corresponding AtomType. * * @param name the qualified name of the type - * @param typeInterface the declared interface of the type */ - record TypeObject(QualifiedName name, AtomTypeInterface typeInterface) - implements TypeRepresentation { + record TypeObject(QualifiedName name) implements TypeRepresentation { @Override public String toString() { return "(type " + name.item() + ")"; @@ -159,7 +176,7 @@ public TypeRepresentation instanceType() { return new ArrowType(TypeRepresentation.ANY, TypeRepresentation.ANY); } - return new AtomType(name, typeInterface); + return new AtomType(name); } @Override @@ -177,6 +194,13 @@ public int hashCode() { } } + /** + * A type describing a module. + * + *

This is similar to TypeObject, but one cannot create instances of a module. + */ + record ModuleReference(QualifiedName name) implements TypeRepresentation {} + /** Represents a type of an unresolved symbol, like `.Foo` or `.bar`. */ record UnresolvedSymbol(String name) implements TypeRepresentation { @Override diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeResolver.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeResolver.java index 71892ea24309..1e3ae3c59df0 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeResolver.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypeResolver.java @@ -12,7 +12,8 @@ import org.enso.compiler.data.BindingsMap; import org.enso.compiler.pass.resolve.Patterns$; import org.enso.compiler.pass.resolve.TypeNames$; -import org.enso.persist.Persistance; +import org.enso.compiler.pass.resolve.TypeSignatures; +import org.enso.compiler.pass.resolve.TypeSignatures$; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import scala.jdk.javaapi.CollectionConverters; @@ -24,43 +25,10 @@ public class TypeResolver { private static final Logger logger = LoggerFactory.getLogger(TypeResolver.class); - TypeRepresentation resolveTypeExpression(Expression type) { + public TypeRepresentation resolveTypeExpression(Expression type) { return switch (type) { - case Name.Literal name -> { - BindingsMap.Resolution resolutionOrNull = - getMetadataOrNull(name, TypeNames$.MODULE$, BindingsMap.Resolution.class); - - if (resolutionOrNull == null) { - // As fallback, try getting from the Patterns pass. - resolutionOrNull = - getMetadataOrNull(name, Patterns$.MODULE$, BindingsMap.Resolution.class); - } - - if (resolutionOrNull != null) { - BindingsMap.ResolvedName target = resolutionOrNull.target(); - yield switch (target) { - case BindingsMap.ResolvedType resolvedType -> resolvedTypeAsAtomType(resolvedType); - case BindingsMap.ResolvedPolyglotSymbol polyglotSymbol -> { - // for now type inference is not able to deal with polyglot types, so we treat them as - // unknown - yield TypeRepresentation.UNKNOWN; - } - default -> { - logger.debug( - "resolveTypeExpression: {} - unexpected resolved name type {}", - name.showCode(), - target.getClass().getCanonicalName()); - yield TypeRepresentation.UNKNOWN; - } - }; - } else { - // TODO investigate - these seem to unexpectedly come up when compiling Standard.Base - logger.debug( - "resolveTypeExpression: {} - Missing expected TypeName resolution metadata", - type.showCode()); - yield TypeRepresentation.UNKNOWN; - } - } + case Name.Literal name -> getResolvedTypeFromBindingsMap(name); + case Name.SelfType selfType -> getResolvedTypeFromBindingsMap(selfType); case Set.Union union -> { var operands = union.operands().map(this::resolveTypeExpression); @@ -90,11 +58,6 @@ yield switch (target) { // We just ignore the error part for now as it's not really checked anywhere. case Type.Error error -> resolveTypeExpression(error.typed()); - case Name.SelfType selfType -> { - // TODO to be handled in further iterations - yield TypeRepresentation.UNKNOWN; - } - case Name.Qualified qualified -> { // TODO to be handled in further iterations yield TypeRepresentation.UNKNOWN; @@ -121,37 +84,57 @@ yield switch (target) { }; } + private TypeRepresentation getResolvedTypeFromBindingsMap(Name name) { + BindingsMap.Resolution resolutionOrNull = + getMetadataOrNull(name, TypeNames$.MODULE$, BindingsMap.Resolution.class); + + if (resolutionOrNull == null) { + // As fallback, try getting from the Patterns pass. + resolutionOrNull = getMetadataOrNull(name, Patterns$.MODULE$, BindingsMap.Resolution.class); + } + + if (resolutionOrNull != null) { + BindingsMap.ResolvedName target = resolutionOrNull.target(); + return switch (target) { + case BindingsMap.ResolvedType resolvedType -> resolvedTypeAsAtomType(resolvedType); + case BindingsMap.ResolvedPolyglotSymbol polyglotSymbol -> { + // for now type inference is not able to deal with polyglot types, so we treat them as + // unknown + yield TypeRepresentation.UNKNOWN; + } + default -> { + logger.debug( + "resolveTypeExpression: {} - unexpected resolved name type {}", + name.showCode(), + target.getClass().getCanonicalName()); + yield TypeRepresentation.UNKNOWN; + } + }; + } else { + // TODO investigate - these seem to unexpectedly come up when compiling Standard.Base + logger.debug( + "resolveTypeExpression: {} - Missing expected TypeName resolution metadata", + name.showCode()); + return TypeRepresentation.UNKNOWN; + } + } + TypeRepresentation.TypeObject resolvedTypeAsTypeObject(BindingsMap.ResolvedType resolvedType) { - var iface = new AtomTypeInterfaceFromBindingsMap(resolvedType.tp()); - return new TypeRepresentation.TypeObject(resolvedType.qualifiedName(), iface); + return new TypeRepresentation.TypeObject(resolvedType.qualifiedName()); } TypeRepresentation resolvedTypeAsAtomType(BindingsMap.ResolvedType resolvedType) { return resolvedTypeAsTypeObject(resolvedType).instanceType(); } - TypeRepresentation buildAtomConstructorType( - TypeRepresentation.TypeObject parentType, AtomTypeInterface.Constructor constructor) { - boolean hasAnyDefaults = - constructor.arguments().stream().anyMatch(AtomTypeInterface.Argument::hasDefaultValue); - if (hasAnyDefaults) { - // TODO implement handling of default arguments - not only ctors will need this! + /** Returns the type ascribed to the given expression, if any. */ + TypeRepresentation getTypeAscription(Expression ir) { + TypeSignatures.Signature ascribedSignature = + getMetadataOrNull(ir, TypeSignatures$.MODULE$, TypeSignatures.Signature.class); + if (ascribedSignature != null) { + return resolveTypeExpression(ascribedSignature.signature()); + } else { return null; } - - var arguments = - constructor.arguments().stream() - .map( - (arg) -> { - var typ = arg.getType(this); - return typ != null ? typ : TypeRepresentation.UNKNOWN; - }) - .toList(); - var resultType = parentType.instanceType(); - return TypeRepresentation.buildFunction(arguments, resultType); - } - - private TypeRepresentation resolveTypeExpression(Persistance.Reference ref) { - return resolveTypeExpression(ref.get(Expression.class)); } } diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/AtomTypeDefinition.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/AtomTypeDefinition.java new file mode 100644 index 000000000000..445f73fe3337 --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/AtomTypeDefinition.java @@ -0,0 +1,43 @@ +package org.enso.compiler.pass.analyse.types.scope; + +import java.util.List; +import org.enso.compiler.pass.analyse.types.TypeRepresentation; + +public final class AtomTypeDefinition { + private final String name; + private final List constructors; + + /** + * Constructs the type definition. + * + * @param name The simple name of the type, e.g. the "Name" in `type Name` + * @param constructors the list of constructor representations + */ + public AtomTypeDefinition(String name, List constructors) { + this.name = name; + this.constructors = constructors; + } + + /** Returns the short name of the type. */ + public String getName() { + return name; + } + + /** + * Returns the constructor of the type with the given name, or {@code null} if a constructor with + * that name does not exist. + */ + public Constructor getConstructor(String name) { + return constructors.stream().filter(c -> c.name().equals(name)).findFirst().orElse(null); + } + + /** + * Represents a constructor of the atom type. + * + * @param name the name of the constructor + * @param isProjectPrivate whether the constructor is project private + * @param type the type ascribed to the constructor, it may be null if it is unknown TODO the type + * will soon be always non-null - once we can handle default arguments + */ + public record Constructor(String name, boolean isProjectPrivate, TypeRepresentation type) {} +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/BuiltinsFallbackScope.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/BuiltinsFallbackScope.java new file mode 100644 index 000000000000..b8186c490ce1 --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/BuiltinsFallbackScope.java @@ -0,0 +1,37 @@ +package org.enso.compiler.pass.analyse.types.scope; + +import org.enso.compiler.pass.analyse.types.BuiltinTypes; +import org.enso.compiler.pass.analyse.types.TypeRepresentation; +import org.enso.pkg.QualifiedName; + +/** + * This is a special scope that notes methods which are always available on Any type. + * + *

They are available even without any imports. + */ +public final class BuiltinsFallbackScope { + private BuiltinsFallbackScope() {} + + private static final StaticModuleScope fallbackAnyScope = constructFallbackScope(); + + public static StaticModuleScope fallbackAnyScope() { + return fallbackAnyScope; + } + + private static StaticModuleScope constructFallbackScope() { + var scopeBuilder = + new StaticModuleScope.Builder(QualifiedName.fromString("Standard.Builtins.Main")); + scopeBuilder.registerMethod(TypeScopeReference.ANY, "to_text", BuiltinTypes.TEXT); + scopeBuilder.registerMethod(TypeScopeReference.ANY, "to_display_text", BuiltinTypes.TEXT); + scopeBuilder.registerMethod(TypeScopeReference.ANY, "pretty", BuiltinTypes.TEXT); + + var any = new TypeRepresentation.TopType(); + scopeBuilder.registerMethod( + TypeScopeReference.ANY, "==", new TypeRepresentation.ArrowType(any, BuiltinTypes.BOOLEAN)); + + var catchType = + new TypeRepresentation.ArrowType(new TypeRepresentation.ArrowType(any, any), any); + scopeBuilder.registerMethod(TypeScopeReference.ANY, "catch_primitive", catchType); + return scopeBuilder.build(); + } +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/ModuleResolver.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/ModuleResolver.java new file mode 100644 index 000000000000..7bac6ea61d12 --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/ModuleResolver.java @@ -0,0 +1,37 @@ +package org.enso.compiler.pass.analyse.types.scope; + +import org.enso.compiler.PackageRepository; +import org.enso.compiler.core.ir.Module; +import org.enso.pkg.QualifiedName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** A helper class that allows to resolve qualified names to loaded modules. */ +public final class ModuleResolver { + private final PackageRepository packageRepository; + private final Logger logger = LoggerFactory.getLogger(ModuleResolver.class); + + public ModuleResolver(PackageRepository packageRepository) { + this.packageRepository = packageRepository; + } + + public Module findModule(QualifiedName name) { + if (packageRepository == null) { + logger.error("Failed to resolve module {} because package repository was null.", name); + return null; + } + + var compilerModuleOpt = packageRepository.getLoadedModule(name.toString()); + if (compilerModuleOpt.isEmpty()) { + return null; + } + + var moduleIr = compilerModuleOpt.get().getIr(); + assert moduleIr != null : "Once a module is found, its IR should be present."; + return moduleIr; + } + + public Module findContainingModule(TypeScopeReference typeScopeReference) { + return findModule(typeScopeReference.relatedModuleName()); + } +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/StaticImportExportScope.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/StaticImportExportScope.java new file mode 100644 index 000000000000..15230d67224f --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/StaticImportExportScope.java @@ -0,0 +1,92 @@ +package org.enso.compiler.pass.analyse.types.scope; + +import org.enso.compiler.common.MethodResolutionAlgorithm; +import org.enso.compiler.pass.analyse.types.TypeRepresentation; +import org.enso.pkg.QualifiedName; + +/** The static counterpart of {@link org.enso.interpreter.runtime.scope.ImportExportScope}. */ +public final class StaticImportExportScope { + // TODO add support for only/hiding once https://github.com/enso-org/enso/issues/10796 is fixed + private final QualifiedName referredModuleName; + + public StaticImportExportScope(QualifiedName referredModuleName) { + this.referredModuleName = referredModuleName; + } + + // This field should not be serialized. + private Resolved cachedResolvedScope = null; + + public Resolved resolve( + ModuleResolver moduleResolver, StaticMethodResolution methodResolutionAlgorithm) { + if (cachedResolvedScope != null) { + return cachedResolvedScope; + } + + var module = moduleResolver.findModule(referredModuleName); + if (module == null) { + throw new IllegalStateException("Could not find module: " + referredModuleName); + } + var moduleScope = StaticModuleScope.forIR(module); + var resolved = new Resolved(moduleScope, methodResolutionAlgorithm); + cachedResolvedScope = resolved; + return resolved; + } + + /** + * The resolved version of the import/export scope. + * + *

The qualified name is replaced with the actual reference to the referred scope. + * + *

This value should not be present in the metadata as it is not suitable for serialization. It + * should be constructed ad-hoc whenever needed. + */ + public static class Resolved { + private final StaticModuleScope referredModuleScope; + private final MethodResolutionAlgorithm< + TypeRepresentation, TypeScopeReference, StaticImportExportScope, StaticModuleScope> + methodResolutionAlgorithm; + + private Resolved( + StaticModuleScope moduleScope, + MethodResolutionAlgorithm< + TypeRepresentation, TypeScopeReference, StaticImportExportScope, StaticModuleScope> + methodResolutionAlgorithm) { + this.referredModuleScope = moduleScope; + this.methodResolutionAlgorithm = methodResolutionAlgorithm; + } + + public TypeRepresentation getMethodForType(TypeScopeReference type, String name) { + // TODO filtering only/hiding (see above) - for now we just return everything + return referredModuleScope.getMethodForType(type, name); + } + + public TypeRepresentation getExportedMethod(TypeScopeReference type, String name) { + // TODO filtering only/hiding (see above) - for now we just return everything + return methodResolutionAlgorithm.getExportedMethod(referredModuleScope, type, name); + } + } + + public QualifiedName getReferredModuleName() { + return referredModuleName; + } + + @Override + public String toString() { + return "StaticImportExportScope{" + referredModuleName + "}"; + } + + @Override + public int hashCode() { + return referredModuleName.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof StaticImportExportScope other)) { + return false; + } + + // TODO once hiding (see above) is added, these filters need to be added too + return referredModuleName.equals(other.referredModuleName); + } +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/StaticMethodResolution.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/StaticMethodResolution.java new file mode 100644 index 000000000000..39aed5259a8b --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/StaticMethodResolution.java @@ -0,0 +1,118 @@ +package org.enso.compiler.pass.analyse.types.scope; + +import java.util.Collection; +import java.util.List; +import org.enso.compiler.common.MethodResolutionAlgorithm; +import org.enso.compiler.pass.analyse.types.TypeRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** The implementation of {@link MethodResolutionAlgorithm} for static analysis. */ +public final class StaticMethodResolution + extends MethodResolutionAlgorithm< + TypeRepresentation, TypeScopeReference, StaticImportExportScope, StaticModuleScope> { + private final ModuleResolver moduleResolver; + private static final Logger LOGGER = LoggerFactory.getLogger(StaticMethodResolution.class); + + public StaticMethodResolution(ModuleResolver moduleResolver) { + this.moduleResolver = moduleResolver; + } + + @Override + protected Collection getImportsFromModuleScope( + StaticModuleScope moduleScope) { + return moduleScope.getImports(); + } + + @Override + protected Collection getExportsFromModuleScope( + StaticModuleScope moduleScope) { + return moduleScope.getExports(); + } + + @Override + protected TypeRepresentation getConversionFromModuleScope( + StaticModuleScope moduleScope, TypeScopeReference target, TypeScopeReference source) { + return moduleScope.getConversionFor(target, source); + } + + @Override + protected TypeRepresentation getMethodFromModuleScope( + StaticModuleScope moduleScope, TypeScopeReference typeScopeReference, String methodName) { + return moduleScope.getMethodForType(typeScopeReference, methodName); + } + + @Override + protected StaticModuleScope findDefinitionScope(TypeScopeReference typeScopeReference) { + var definitionModule = moduleResolver.findContainingModule(typeScopeReference); + if (definitionModule != null) { + return StaticModuleScope.forIR(definitionModule); + } else { + if (typeScopeReference.equals(TypeScopeReference.ANY)) { + // We have special handling for ANY: it points to Standard.Base.Any.Any, but that may not + // always be imported. + // The runtime falls back to Standard.Builtins.Main, but that modules does not contain any + // type information, so it is not useful for us. + // Instead we fall back to the hardcoded definitions of the 5 builtins of Any. + return BuiltinsFallbackScope.fallbackAnyScope(); + } else { + LOGGER.error("Could not find declaration module of type: {}", typeScopeReference); + return null; + } + } + } + + @Override + protected TypeRepresentation getMethodForTypeFromScope( + StaticImportExportScope scope, TypeScopeReference typeScopeReference, String methodName) { + return scope.resolve(moduleResolver, this).getMethodForType(typeScopeReference, methodName); + } + + @Override + protected TypeRepresentation getExportedMethodFromScope( + StaticImportExportScope scope, TypeScopeReference typeScopeReference, String methodName) { + return scope.resolve(moduleResolver, this).getExportedMethod(typeScopeReference, methodName); + } + + @Override + protected TypeRepresentation getConversionFromScope( + StaticImportExportScope scope, TypeScopeReference target, TypeScopeReference source) { + // TODO conversions in static analysis + return null; + } + + @Override + protected TypeRepresentation getExportedConversionFromScope( + StaticImportExportScope scope, TypeScopeReference target, TypeScopeReference source) { + // TODO conversions in static analysis + return null; + } + + @Override + protected TypeRepresentation onMultipleDefinitionsFromImports( + String methodName, + List> methodFromImports) { + if (LOGGER.isDebugEnabled()) { + var foundImportNames = methodFromImports.stream().map(MethodFromImport::origin); + LOGGER.debug("Method {} is coming from multiple imports: {}", methodName, foundImportNames); + } + + long foundTypesCount = + methodFromImports.stream().map(MethodFromImport::resolutionResult).distinct().count(); + if (foundTypesCount > 1) { + List foundTypesWithOrigins = + methodFromImports.stream() + .distinct() + .map(m -> m.resolutionResult() + " from " + m.origin()) + .toList(); + LOGGER.error( + "Method {} is coming from multiple imports with different types: {}", + methodName, + foundTypesWithOrigins); + return null; + } else { + // If all types are the same, just return the first one + return methodFromImports.get(0).resolutionResult(); + } + } +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/StaticModuleScope.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/StaticModuleScope.java new file mode 100644 index 000000000000..db0841a6a528 --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/StaticModuleScope.java @@ -0,0 +1,174 @@ +package org.enso.compiler.pass.analyse.types.scope; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.enso.compiler.MetadataInteropHelpers; +import org.enso.compiler.core.CompilerStub; +import org.enso.compiler.core.ir.Module; +import org.enso.compiler.core.ir.ProcessingPass; +import org.enso.compiler.pass.analyse.types.TypeRepresentation; +import org.enso.pkg.QualifiedName; +import scala.Option; + +/** + * This is a sibling to the ModuleScope. + * + *

The ModuleScope is the runtime representation of a module, optimized for fast runtime + * dispatch. The StaticModuleScope is an analogous structure, that can be used by static analysis + * passes at compilation time. + * + *

It is also similar to the BindingsMap structure. In fact, it may be possible to merge the two + * modules in the future, as StaticModuleScope is a more general variant. The BindingsMap only deals + * with Types and their Constructors that are used during static resolution of some names. This + * class also keeps track of all defined methods, to facilitate type checking. I'm keeping these + * separate for now as it is easier to create a prototype that way. If later we find out they have + * enough of similarity, we should merge them. + */ +public final class StaticModuleScope implements ProcessingPass.Metadata { + private final QualifiedName moduleName; + private final TypeScopeReference associatedType; + private final List imports; + private final List exports; + private final Map typesDefinedHere; + private final Map> methods; + + private StaticModuleScope( + QualifiedName moduleName, + TypeScopeReference associatedType, + List imports, + List exports, + Map typesDefinedHere, + Map> methods) { + this.moduleName = moduleName; + this.associatedType = associatedType; + this.imports = imports; + this.exports = exports; + this.typesDefinedHere = typesDefinedHere; + this.methods = methods; + } + + static final class Builder { + private final QualifiedName moduleName; + private final TypeScopeReference associatedType; + private final List imports = new ArrayList<>(); + private final List exports = new ArrayList<>(); + private final Map typesDefinedHere = new HashMap<>(); + private final Map> methods = + new HashMap<>(); + + private boolean sealed = false; + + private void checkSealed() { + if (sealed) { + throw new IllegalStateException( + "`build` method has already been called, this builder should no longer be modified."); + } + } + + Builder(QualifiedName moduleName) { + this.moduleName = moduleName; + this.associatedType = TypeScopeReference.moduleAssociatedType(moduleName); + } + + public StaticModuleScope build() { + sealed = true; + return new StaticModuleScope( + moduleName, + associatedType, + Collections.unmodifiableList(imports), + Collections.unmodifiableList(exports), + Collections.unmodifiableMap(typesDefinedHere), + Collections.unmodifiableMap(methods)); + } + + QualifiedName getModuleName() { + return moduleName; + } + + public TypeScopeReference getAssociatedType() { + return associatedType; + } + + void registerType(AtomTypeDefinition type) { + checkSealed(); + var previous = typesDefinedHere.putIfAbsent(type.getName(), type); + if (previous != null) { + throw new IllegalStateException("Type already defined: " + type.getName()); + } + } + + void registerMethod(TypeScopeReference parentType, String name, TypeRepresentation type) { + checkSealed(); + var typeMethods = methods.computeIfAbsent(parentType, k -> new HashMap<>()); + typeMethods.put(name, type); + } + + public void addImport(StaticImportExportScope importScope) { + checkSealed(); + imports.add(importScope); + } + + public void addExport(StaticImportExportScope exportScope) { + checkSealed(); + exports.add(exportScope); + } + } + + public TypeScopeReference getAssociatedType() { + return associatedType; + } + + public static StaticModuleScope forIR(Module module) { + return MetadataInteropHelpers.getMetadata( + module, StaticModuleScopeAnalysis.INSTANCE, StaticModuleScope.class); + } + + public TypeRepresentation getMethodForType(TypeScopeReference type, String name) { + var typeMethods = methods.get(type); + if (typeMethods == null) { + return null; + } + + return typeMethods.get(name); + } + + @Override + public String metadataName() { + return "StaticModuleScope"; + } + + @Override + public ProcessingPass.Metadata prepareForSerialization(CompilerStub compiler) { + return this; + } + + @Override + public Option restoreFromSerialization(CompilerStub compiler) { + return Option.apply(this); + } + + @Override + public Option duplicate() { + return Option.empty(); + } + + public List getImports() { + return imports; + } + + public List getExports() { + return exports; + } + + public TypeRepresentation getConversionFor(TypeScopeReference target, TypeScopeReference source) { + // TODO conversions in static analysis + return null; + } + + public AtomTypeDefinition getType(String name) { + return typesDefinedHere.get(name); + } +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/StaticModuleScopeAnalysis.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/StaticModuleScopeAnalysis.java new file mode 100644 index 000000000000..c31612ac106e --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/StaticModuleScopeAnalysis.java @@ -0,0 +1,245 @@ +package org.enso.compiler.pass.analyse.types.scope; + +import static org.enso.compiler.MetadataInteropHelpers.getMetadata; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.enso.compiler.MetadataInteropHelpers; +import org.enso.compiler.common.BuildScopeFromModuleAlgorithm; +import org.enso.compiler.context.InlineContext; +import org.enso.compiler.context.ModuleContext; +import org.enso.compiler.core.IR; +import org.enso.compiler.core.ir.Expression; +import org.enso.compiler.core.ir.Module; +import org.enso.compiler.core.ir.module.scope.Definition; +import org.enso.compiler.core.ir.module.scope.definition.Method; +import org.enso.compiler.data.BindingsMap; +import org.enso.compiler.pass.IRPass; +import org.enso.compiler.pass.IRProcessingPass; +import org.enso.compiler.pass.analyse.BindingAnalysis$; +import org.enso.compiler.pass.analyse.types.InferredType; +import org.enso.compiler.pass.analyse.types.TypeInferencePropagation; +import org.enso.compiler.pass.analyse.types.TypeInferenceSignatures; +import org.enso.compiler.pass.analyse.types.TypeRepresentation; +import org.enso.compiler.pass.analyse.types.TypeResolver; +import org.enso.compiler.pass.resolve.FullyQualifiedNames$; +import org.enso.compiler.pass.resolve.GlobalNames$; +import org.enso.compiler.pass.resolve.TypeNames$; +import org.enso.pkg.QualifiedName; +import org.enso.scala.wrapper.ScalaConversions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import scala.Option; +import scala.collection.immutable.Seq; + +public class StaticModuleScopeAnalysis implements IRPass { + public static final StaticModuleScopeAnalysis INSTANCE = new StaticModuleScopeAnalysis(); + private final Logger logger = LoggerFactory.getLogger(StaticModuleScopeAnalysis.class); + + private final TypeResolver typeResolver = new TypeResolver(); + + private StaticModuleScopeAnalysis() {} + + @Override + public String toString() { + return "StaticModuleScopeAnalysis"; + } + + @Override + public Seq precursorPasses() { + List passes = + List.of( + GlobalNames$.MODULE$, + BindingAnalysis$.MODULE$, + FullyQualifiedNames$.MODULE$, + TypeNames$.MODULE$, + TypeInferenceSignatures.INSTANCE); + return ScalaConversions.seq(passes); + } + + @Override + public Seq invalidatedPasses() { + List passes = List.of(TypeInferencePropagation.INSTANCE); + return ScalaConversions.seq(passes); + } + + @Override + public Module runModule(Module ir, ModuleContext moduleContext) { + // This has a lot in common with IrToTruffle::processModule - we may want to extract some common + // parts if it will make sense. + StaticModuleScope.Builder scopeBuilder = new StaticModuleScope.Builder(moduleContext.getName()); + BindingsMap bindingsMap = getMetadata(ir, BindingAnalysis$.MODULE$, BindingsMap.class); + + BuildStaticModuleScope buildScopeAlgorithm = new BuildStaticModuleScope(scopeBuilder); + buildScopeAlgorithm.processModule(ir, bindingsMap); + StaticModuleScope scope = scopeBuilder.build(); + ir.passData().update(INSTANCE, scope); + return ir; + } + + @Override + public Expression runExpression(Expression ir, InlineContext inlineContext) { + // Nothing to do - this pass only works on module-level. + return ir; + } + + private final class BuildStaticModuleScope + extends BuildScopeFromModuleAlgorithm { + private StaticModuleScope.Builder scopeBuilder; + + private BuildStaticModuleScope(StaticModuleScope.Builder scopeBuilder) { + this.scopeBuilder = scopeBuilder; + } + + @Override + protected void registerExport(StaticImportExportScope exportScope) { + scopeBuilder.addExport(exportScope); + } + + @Override + protected void registerImport(StaticImportExportScope importScope) { + scopeBuilder.addImport(importScope); + } + + @Override + protected TypeScopeReference getTypeAssociatedWithCurrentScope() { + return scopeBuilder.getAssociatedType(); + } + + @Override + protected void processPolyglotJavaImport(String visibleName, String javaClassName) { + // Currently nothing to do here, as we don't resolve methods on Java types. Assigning them + // with Any should be good enough. + // TODO: we may want a test making sure that we don't do any false positive warnings + } + + @Override + protected void processConversion(Method.Conversion conversion) { + // TODO conversion handling is not implemented yet in the type checker + } + + @Override + protected void processMethodDefinition(Method.Explicit method) { + var typeScope = getTypeDefiningMethod(method); + if (typeScope == null) { + logger.warn( + "Failed to process method {}, because its type scope could not be resolved.", + method.methodReference().showCode()); + return; + } + var typeFromSignature = + MetadataInteropHelpers.getMetadataOrNull( + method, TypeInferenceSignatures.INSTANCE, InferredType.class); + var type = typeFromSignature != null ? typeFromSignature.type() : TypeRepresentation.UNKNOWN; + var name = method.methodReference().methodName().name(); + scopeBuilder.registerMethod(typeScope, name, type); + } + + @Override + protected void processTypeDefinition(Definition.Type typ) { + QualifiedName qualifiedName = scopeBuilder.getModuleName().createChild(typ.name().name()); + TypeRepresentation.TypeObject typeObject = new TypeRepresentation.TypeObject(qualifiedName); + List constructors = + ScalaConversions.asJava(typ.members()).stream() + .map( + constructorDef -> { + TypeRepresentation type = buildAtomConstructorType(typeObject, constructorDef); + return new AtomTypeDefinition.Constructor( + constructorDef.name().name(), constructorDef.isPrivate(), type); + }) + .toList(); + + AtomTypeDefinition atomTypeDefinition = + new AtomTypeDefinition(typ.name().name(), constructors); + var atomTypeScope = TypeScopeReference.atomType(qualifiedName); + scopeBuilder.registerType(atomTypeDefinition); + registerFieldGetters(scopeBuilder, atomTypeScope, typ); + } + + private TypeRepresentation buildAtomConstructorType( + TypeRepresentation.TypeObject associatedType, Definition.Data constructorDef) { + boolean hasDefaults = constructorDef.arguments().exists(a -> a.defaultValue().isDefined()); + if (hasDefaults) { + // TODO implement handling of default arguments - not only ctors will need this! + return null; + } + + var arguments = + constructorDef + .arguments() + .map( + (arg) -> { + Option typ = arg.ascribedType(); + if (typ.isEmpty()) { + return TypeRepresentation.UNKNOWN; + } + + var resolvedType = typeResolver.resolveTypeExpression(typ.get()); + assert resolvedType != null; + return resolvedType; + }) + .toList(); + var resultType = associatedType.instanceType(); + return TypeRepresentation.buildFunction(ScalaConversions.asJava(arguments), resultType); + } + + @Override + protected TypeScopeReference associatedTypeFromResolvedModule( + BindingsMap.ResolvedModule module) { + return TypeScopeReference.moduleAssociatedType(module.qualifiedName()); + } + + @Override + protected TypeScopeReference associatedTypeFromResolvedType( + BindingsMap.ResolvedType type, boolean isStatic) { + return TypeScopeReference.atomType(type.qualifiedName(), isStatic); + } + + @Override + protected StaticImportExportScope buildExportScope(BindingsMap.ExportedModule exportedModule) { + return new StaticImportExportScope(exportedModule.module().qualifiedName()); + } + + @Override + protected StaticImportExportScope buildImportScope( + BindingsMap.ResolvedImport resolvedImport, BindingsMap.ResolvedModule resolvedModule) { + return new StaticImportExportScope(resolvedModule.qualifiedName()); + } + } + + @Override + public T updateMetadataInDuplicate(T sourceIr, T copyOfIr) { + return IRPass.super.updateMetadataInDuplicate(sourceIr, copyOfIr); + } + + /** + * Registers getters for fields of the given type. + * + *

This should be consistent with logic with AtomConstructor.collectFieldAccessors. + */ + private void registerFieldGetters( + StaticModuleScope.Builder scope, + TypeScopeReference typeScope, + Definition.Type typeDefinition) { + Map> fieldTypes = new HashMap<>(); + for (var constructorDef : ScalaConversions.asJava(typeDefinition.members())) { + for (var argumentDef : ScalaConversions.asJava(constructorDef.arguments())) { + String fieldName = argumentDef.name().name(); + TypeRepresentation fieldType = + argumentDef + .ascribedType() + .map(typeResolver::resolveTypeExpression) + .getOrElse(() -> TypeRepresentation.UNKNOWN); + fieldTypes.computeIfAbsent(fieldName, k -> new ArrayList<>()).add(fieldType); + } + } + + for (var entry : fieldTypes.entrySet()) { + String fieldName = entry.getKey(); + TypeRepresentation mergedType = TypeRepresentation.buildSimplifiedSumType(entry.getValue()); + scope.registerMethod(typeScope, fieldName, mergedType); + } + } +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/TypeHierarchy.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/TypeHierarchy.java new file mode 100644 index 000000000000..3036fc71d890 --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/TypeHierarchy.java @@ -0,0 +1,42 @@ +package org.enso.compiler.pass.analyse.types.scope; + +import org.enso.compiler.pass.analyse.types.BuiltinTypes; +import org.enso.pkg.QualifiedName; + +/** + * A helper that encapsulates the hierarchy of types. + * + *

    + *
  • Module types and Any, have no parents. + *
  • The types Integer and Float have Number as their parent. + *
  • Any other type has Any as its parent. + *
+ * + * This should be aligned with Type.allTypes in the interpreter. + */ +public class TypeHierarchy { + private TypeHierarchy() {} + + public static TypeScopeReference getParent(TypeScopeReference type) { + switch (type.getKind()) { + case MODULE_ASSOCIATED_TYPE: + return null; + case ATOM_TYPE: + var name = type.getName(); + if (BuiltinTypes.isAny(name)) { + // Any has no more parents + return null; + } + + if (BuiltinTypes.isInteger(name) || BuiltinTypes.isFloat(name)) { + return TypeScopeReference.atomType(QualifiedName.fromString(BuiltinTypes.FQN_NUMBER)); + } + + return TypeScopeReference.ANY; + case ATOM_EIGEN_TYPE: + return TypeScopeReference.ANY; + default: + throw new RuntimeException("Unknown type kind: " + type.getKind()); + } + } +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/TypeScopeReference.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/TypeScopeReference.java new file mode 100644 index 000000000000..8361151385a1 --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/scope/TypeScopeReference.java @@ -0,0 +1,96 @@ +package org.enso.compiler.pass.analyse.types.scope; + +import org.enso.compiler.pass.analyse.types.TypeRepresentation; +import org.enso.pkg.QualifiedName; + +/** + * An identity of a Type (corresponds to {@link org.enso.interpreter.runtime.data.Type}). + * + *

It can be one of three things: + * + *

    + *
  • An atom type + *
  • An eigentype of an atom type - this will hold the type's static methods + *
  • An associated type of a module + *
+ */ +public final class TypeScopeReference { + private final QualifiedName name; + + private final Kind kind; + + private TypeScopeReference(QualifiedName name, Kind kind) { + this.name = name; + this.kind = kind; + } + + public static TypeScopeReference moduleAssociatedType(QualifiedName moduleName) { + return new TypeScopeReference(moduleName, Kind.MODULE_ASSOCIATED_TYPE); + } + + public static TypeScopeReference atomType(QualifiedName atomTypeName) { + return new TypeScopeReference(atomTypeName, Kind.ATOM_TYPE); + } + + public static TypeScopeReference atomEigenType(QualifiedName atomTypeName) { + return new TypeScopeReference(atomTypeName, Kind.ATOM_EIGEN_TYPE); + } + + public static TypeScopeReference atomType(QualifiedName atomTypeName, boolean staticCall) { + return staticCall ? atomEigenType(atomTypeName) : atomType(atomTypeName); + } + + @Override + public int hashCode() { + return name.hashCode() + kind.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TypeScopeReference other)) { + return false; + } + + return name.equals(other.name) && kind.equals(other.kind); + } + + enum Kind { + MODULE_ASSOCIATED_TYPE, + ATOM_TYPE, + ATOM_EIGEN_TYPE + } + + QualifiedName getName() { + return name; + } + + QualifiedName relatedModuleName() { + switch (kind) { + case MODULE_ASSOCIATED_TYPE: + return name; + case ATOM_TYPE: + case ATOM_EIGEN_TYPE: + var parent = name.getParent(); + assert parent.isDefined(); + return parent.get(); + default: + throw new IllegalStateException("Unexpected value: " + kind); + } + } + + Kind getKind() { + return kind; + } + + @Override + public String toString() { + return switch (kind) { + case MODULE_ASSOCIATED_TYPE -> "ModuleAssociatedType(" + name + ")"; + case ATOM_TYPE -> "AtomType(" + name + ")"; + case ATOM_EIGEN_TYPE -> "AtomEigenType(" + name + ")"; + }; + } + + public static TypeScopeReference ANY = + TypeScopeReference.atomType(TypeRepresentation.ANY.getAssociatedType()); +} diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/Compiler.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/Compiler.scala index 467306ebfbf5..3484c211d670 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/Compiler.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/Compiler.scala @@ -133,12 +133,17 @@ class Compiler( * * @param shouldCompileDependencies whether compilation should also compile * the dependencies of the requested package + * @param shouldWriteCache whether the compilation results should be written + * to the cache; if set to False, a 'lint' compilation + * will be performed, reporting any problems, + * but no results will be written * @param useGlobalCacheLocations whether or not the compilation result should * be written to the global cache * @return future to track subsequent serialization of the library */ def compile( shouldCompileDependencies: Boolean, + shouldWriteCache: Boolean, useGlobalCacheLocations: Boolean ): Future[java.lang.Boolean] = { getPackageRepository.getMainProjectPackage match { @@ -181,11 +186,13 @@ class Compiler( shouldCompileDependencies ) - context.serializeLibrary( - this, - pkg.libraryName, - useGlobalCacheLocations - ) + if (shouldWriteCache) { + context.serializeLibrary( + this, + pkg.libraryName, + useGlobalCacheLocations + ) + } else CompletableFuture.completedFuture(true) } } } @@ -358,6 +365,33 @@ class Compiler( } } + requiredModules.foreach { module => + if ( + !context + .getCompilationStage(module) + .isAtLeast( + CompilationStage.AFTER_TYPE_INFERENCE_PASSES + ) + ) { + + val moduleContext = ModuleContext( + module = module, + freshNameSupply = Some(freshNameSupply), + compilerConfig = config, + pkgRepo = Some(packageRepository) + ) + val compilerOutput = + runFinalTypeInferencePasses(context.getIr(module), moduleContext) + context.updateModule( + module, + { u => + u.ir(compilerOutput) + u.compilationStage(CompilationStage.AFTER_TYPE_INFERENCE_PASSES) + } + ) + } + } + runErrorHandling(requiredModules) val requiredModulesWithScope = requiredModules.map { module => @@ -808,6 +842,21 @@ class Compiler( passManager.runPassesOnModule(ir, moduleContext, passes.globalTypingPasses) } + /** Runs the final type inference passes, if they are enabled. + * + * If they are not enabled, it will not run any passes. + */ + private def runFinalTypeInferencePasses( + ir: IRModule, + moduleContext: ModuleContext + ): IRModule = { + passManager.runPassesOnModule( + ir, + moduleContext, + passes.typeInferenceFinalPasses + ) + } + /** Runs the various compiler passes in an inline context. * * @param ir the compiler intermediate representation to transform diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala index d9f75cba68eb..9d0f42809031 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala @@ -4,7 +4,11 @@ import org.enso.compiler.data.CompilerConfig import org.enso.compiler.dump.IRDumperPass import org.enso.compiler.pass.PassConfiguration._ import org.enso.compiler.pass.analyse._ -import org.enso.compiler.pass.analyse.types.TypeInference +import org.enso.compiler.pass.analyse.types.scope.StaticModuleScopeAnalysis +import org.enso.compiler.pass.analyse.types.{ + TypeInferencePropagation, + TypeInferenceSignatures +} import org.enso.compiler.pass.desugar._ import org.enso.compiler.pass.lint.{ ModuleNameConflicts, @@ -102,13 +106,22 @@ class Passes(config: CompilerConfig) { List(UnusedBindings, NoSelfInStatic) }) ++ (if (config.staticTypeInferenceEnabled) { List( - TypeInference.INSTANCE + TypeInferenceSignatures.INSTANCE, + StaticModuleScopeAnalysis.INSTANCE ) } else Nil) ++ (if (config.dumpIrs) { List(IRDumperPass.INSTANCE) } else Nil) ) + val typeInferenceFinalPasses = new PassGroup( + if (config.staticTypeInferenceEnabled) { + List( + TypeInferencePropagation.INSTANCE + ) + } else List() + ) + /** A list of the compiler phases, in the order they should be run. * * The pass manager checks at runtime whether the provided order respects the @@ -119,7 +132,8 @@ class Passes(config: CompilerConfig) { List( moduleDiscoveryPasses, globalTypingPasses, - functionBodyPasses + functionBodyPasses, + typeInferenceFinalPasses ) /** The ordered representation of all passes run by the compiler. */ diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/InlineContext.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/InlineContext.scala index af96f978204a..2ae1db75f164 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/InlineContext.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/InlineContext.scala @@ -8,7 +8,7 @@ import org.enso.compiler.pass.PassConfiguration /** A type containing the information about the execution context for an inline * expression. * - * @param module the module in which the expression is being executed + * @param moduleContext the module in which the expression is being executed * @param compilerConfig the compiler configuration * @param localScope the local scope in which the expression is being executed * @param isInTailPosition whether or not the inline expression occurs in tail @@ -18,7 +18,7 @@ import org.enso.compiler.pass.PassConfiguration * @param pkgRepo the compiler's package repository */ case class InlineContext( - private val module: ModuleContext, + private val moduleContext: ModuleContext, compilerConfig: CompilerConfig, localScope: Option[LocalScope] = None, isInTailPosition: Option[Boolean] = None, @@ -26,14 +26,13 @@ case class InlineContext( passConfiguration: Option[PassConfiguration] = None, pkgRepo: Option[PackageRepository] = None ) extends AutoCloseable { - def bindingsAnalysis(): BindingsMap = module.bindingsAnalysis() - def getModule() = module.module + def bindingsAnalysis(): BindingsMap = moduleContext.bindingsAnalysis() + def getModule() = moduleContext.module def close(): Unit = { this.localScope .foreach(_.scope.removeScopeFromParent()) } - } object InlineContext { @@ -57,7 +56,7 @@ object InlineContext { ): InlineContext = { InlineContext( localScope = Option(localScope), - module = ModuleContext(module, compilerConfig), + moduleContext = ModuleContext(module, compilerConfig), isInTailPosition = isInTailPosition, compilerConfig = compilerConfig, pkgRepo = pkgRepo @@ -73,7 +72,7 @@ object InlineContext { def fromModuleContext(moduleContext: ModuleContext): InlineContext = { InlineContext( localScope = None, - module = moduleContext, + moduleContext = moduleContext, isInTailPosition = None, freshNameSupply = moduleContext.freshNameSupply, passConfiguration = moduleContext.passConfiguration, diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala index 9c8753aad043..c9df1f19a5cb 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala @@ -4,18 +4,15 @@ import org.enso.compiler.PackageRepository import org.enso.compiler.PackageRepository.ModuleMap import org.enso.compiler.context.CompilerContext.Module import org.enso.compiler.core.Implicits.AsMetadata -import org.enso.compiler.core.ir +import org.enso.compiler.core.{ir, CompilerError} import org.enso.compiler.core.ir.expression.errors -import org.enso.compiler.data.BindingsMap.{DefinedEntity, ModuleReference} -import org.enso.compiler.core.CompilerError -import org.enso.compiler.core.ir.Expression import org.enso.compiler.core.ir.module.scope.Definition +import org.enso.compiler.data.BindingsMap.{DefinedEntity, ModuleReference} import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.analyse.BindingAnalysis import org.enso.compiler.pass.resolve.MethodDefinitions -import org.enso.persist.Persistance.Reference -import org.enso.pkg.QualifiedName import org.enso.editions.LibraryName +import org.enso.pkg.QualifiedName import java.io.ObjectOutputStream import scala.annotation.unused @@ -595,15 +592,7 @@ object BindingsMap { def allFieldsDefaulted: Boolean = arguments.forall(_.hasDefaultValue) } - case class Argument( - name: String, - hasDefaultValue: Boolean, - typReference: Reference[Expression] - ) { - def typ(): Option[Expression] = Option( - typReference.get(classOf[Expression]) - ) - } + case class Argument(name: String, hasDefaultValue: Boolean) /** A representation of a sum type * @@ -629,15 +618,9 @@ object BindingsMap { Cons( m.name.name, m.arguments.map { arg => - val ascribedType: Reference[Expression] = - arg.ascribedType match { - case Some(value) => Reference.of(value, true) - case None => Reference.none() - } BindingsMap.Argument( arg.name.name, - arg.defaultValue.isDefined, - ascribedType + arg.defaultValue.isDefined ) }, m.isPrivate diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/PassManager.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/PassManager.scala index 9c643deeb200..8f0bbeaa99ea 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/PassManager.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/PassManager.scala @@ -1,11 +1,9 @@ package org.enso.compiler.pass -import org.enso.common.{Asserts, CompilationStage} import org.slf4j.LoggerFactory import org.enso.compiler.context.{InlineContext, ModuleContext} import org.enso.compiler.core.ir.{Expression, Module} import org.enso.compiler.core.{CompilerError, IR} -import org.enso.compiler.pass.analyse.BindingAnalysis import scala.collection.mutable.ListBuffer @@ -73,8 +71,6 @@ class PassManager( moduleContext: ModuleContext, passGroup: PassGroup ): Module = { - Asserts.assertInJvm(validateConsistency(ir, moduleContext)) - if (!passes.contains(passGroup)) { throw new CompilerError("Cannot run an unvalidated pass group.") } @@ -247,51 +243,6 @@ class PassManager( ix - totalLength == indexOfPassInGroup } - /** Validates consistency between the IR accessible via `moduleContext` and `ir`. - * There is no way to enforce this consistency statically. - * Should be called only iff assertions are enabled. - * @return true if they are consistent, otherwise throws [[AssertionError]]. - */ - private def validateConsistency( - ir: Module, - moduleContext: ModuleContext - ): Boolean = { - def hex(obj: Object): String = { - if (obj != null) { - val hexStr = Integer.toHexString(System.identityHashCode(obj)) - val className = obj.getClass.getSimpleName - s"$className@${hexStr}" - } else { - "null" - } - } - - if ( - moduleContext.module.getCompilationStage.isAtLeast( - CompilationStage.AFTER_PARSING - ) - ) { - if (!(moduleContext.module.getIr eq ir)) { - throw new AssertionError( - "Mismatch of IR between ModuleContext and IR in module '" + moduleContext - .getName() + "'. " + - s"IR from moduleContext: ${hex(moduleContext.module.getIr)}, IR from module: ${hex(ir)}" - ) - } - val bmFromCtx = moduleContext.bindingsAnalysis() - val bmFromMeta = ir.passData.get(BindingAnalysis) - if (bmFromMeta.isDefined || bmFromCtx != null) { - Asserts.assertInJvm( - bmFromCtx eq bmFromMeta.get, - s"BindingsMap mismatch between ModuleContext and IR in module '" + - moduleContext.getName() + "'. " + - s"BindingsMap from moduleContext: ${hex(bmFromCtx)}, BindingsMap from IR: ${hex(bmFromMeta.get)}" - ) - } - } - true - } - /** Updates the metadata in a copy of the IR when updating that metadata * requires global state. * diff --git a/engine/runtime-instrument-common/src/test/scala/org/enso/compiler/test/CompilerTestSetup.scala b/engine/runtime-instrument-common/src/test/scala/org/enso/compiler/test/CompilerTestSetup.scala index 216260e9eca1..62edc72e4fcf 100644 --- a/engine/runtime-instrument-common/src/test/scala/org/enso/compiler/test/CompilerTestSetup.scala +++ b/engine/runtime-instrument-common/src/test/scala/org/enso/compiler/test/CompilerTestSetup.scala @@ -194,7 +194,7 @@ trait CompilerTestSetup { compilerConfig = compilerConfig ) InlineContext( - module = mc, + moduleContext = mc, freshNameSupply = freshNameSupply, passConfiguration = passConfiguration, localScope = localScope, diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/SerdeCompilerTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/SerdeCompilerTest.java index d070c83bc253..437419b7a6e4 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/SerdeCompilerTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/SerdeCompilerTest.java @@ -69,7 +69,7 @@ private void parseSerializedModule(String projectName, String forbiddenMessage) futures.add(future); return null; }); - futures.add(compiler.compile(false, true)); + futures.add(compiler.compile(false, true, true)); for (var f : futures) { var persisted = f.get(10, TimeUnit.SECONDS); assertEquals("Fib_Test library has been fully persisted", true, persisted); diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/SerializationManagerTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/SerializationManagerTest.java index 24e0595f3653..e0dfa4f6b5d3 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/SerializationManagerTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/SerializationManagerTest.java @@ -89,7 +89,7 @@ public void serializeLibrarySuggestions() Object result = ensoContext .getCompiler() - .compile(false, false) + .compile(false, true, false) .get(COMPILE_TIMEOUT_SECONDS, TimeUnit.SECONDS); Assert.assertEquals(Boolean.TRUE, result); diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/StaticAnalysisTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/StaticAnalysisTest.java new file mode 100644 index 000000000000..ae1e48489a28 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/StaticAnalysisTest.java @@ -0,0 +1,117 @@ +package org.enso.compiler.test; + +import com.oracle.truffle.api.TruffleFile; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import org.enso.common.LanguageInfo; +import org.enso.common.MethodNames; +import org.enso.common.RuntimeOptions; +import org.enso.compiler.core.ir.Module; +import org.enso.editions.LibraryName; +import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.util.TruffleFileSystem; +import org.enso.interpreter.test.InterpreterContext; +import org.enso.pkg.Config; +import org.enso.pkg.Contact; +import org.enso.pkg.Package; +import org.enso.pkg.QualifiedName; +import org.graalvm.polyglot.Source; +import scala.Option; +import scala.jdk.javaapi.CollectionConverters; + +public abstract class StaticAnalysisTest { + + /** + * The interpreter context is needed here as it ensures initialization of everything needed to + * perform imports resolution, including PackageRepository. + * + *

Ideally, the tests for the static analysis capabilities of the compiler should _not_ depend + * on the Graal runtime context, as they should be runnable in other contexts - i.e. in a Visual + * Studio Code language server. + */ + private final InterpreterContext interpreterContext = + new InterpreterContext( + (builder) -> + builder + .option(RuntimeOptions.ENABLE_STATIC_ANALYSIS, "true") + .option(RuntimeOptions.LOG_LEVEL, Level.INFO.getName()) + .option(RuntimeOptions.LOG_LEVEL, Level.SEVERE.getName()) + .out(OutputStream.nullOutputStream()) + .err(OutputStream.nullOutputStream())); + + private final EnsoContext langCtx = + interpreterContext + .ctx() + .getBindings(LanguageInfo.ID) + .invokeMember(MethodNames.TopScope.LEAK_CONTEXT) + .asHostObject(); + + private final Map> syntheticTestPackages = new HashMap<>(); + + protected Module compile(Source src) { + String suffix = ".enso"; + String name = src.getName(); + if (!name.endsWith(suffix)) { + throw new IllegalArgumentException("Source name must end with " + suffix); + } + QualifiedName qualifiedName = + QualifiedName.fromString(name.substring(0, name.length() - suffix.length())); + + var packageRepository = langCtx.getPackageRepository(); + Package pkg = null; + + // If the module name is supposed to be put in a project, we register a synthetic project entry + // for it + if (qualifiedName.path().length() >= 2) { + var libraryName = + new LibraryName(qualifiedName.path().apply(0), qualifiedName.path().apply(1)); + if (!packageRepository.isPackageLoaded(libraryName)) { + // We are able only to register a synthetic package without associated Package<> object, but + // perhaps that's fine. + packageRepository.registerSyntheticPackage(libraryName.namespace(), libraryName.name()); + assert packageRepository.isPackageLoaded(libraryName); + } + + pkg = makeSyntheticPackageForTestProject(libraryName); + } + + // This creates the module and also registers it in the scope, so that import resolution will + // see it. + var module = + langCtx.getTopScope().createModule(qualifiedName, pkg, src.getCharacters().toString()); + langCtx.getCompiler().run(module.asCompilerModule()); + return module.getIr(); + } + + private Package makeSyntheticPackageForTestProject(LibraryName name) { + return syntheticTestPackages.computeIfAbsent( + name, + (unused) -> { + try { + var tmpRoot = Files.createTempDirectory("test-project-" + name); + TruffleFile root = langCtx.getPublicTruffleFile(tmpRoot.toString()); + List contacts = List.of(); + Config initialConfig = + new Config( + name.name(), + Option.empty(), + name.namespace(), + "0.0.0", + "", + CollectionConverters.asScala(contacts).toList(), + CollectionConverters.asScala(contacts).toList(), + Option.empty(), + true, + Option.empty()); + return new Package<>(root, initialConfig, TruffleFileSystem.INSTANCE); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/TypeInferenceTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/TypeInferenceTest.java index 33aa8b37b74f..31bac9136f8f 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/TypeInferenceTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/TypeInferenceTest.java @@ -1,44 +1,41 @@ package org.enso.compiler.test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; -import org.enso.compiler.Compiler; -import org.enso.compiler.Passes; -import org.enso.compiler.context.FreshNameSupply; -import org.enso.compiler.context.ModuleContext; +import org.enso.common.RuntimeOptions; import org.enso.compiler.core.IR; -import org.enso.compiler.core.ir.Diagnostic; -import org.enso.compiler.core.ir.Expression; import org.enso.compiler.core.ir.Module; import org.enso.compiler.core.ir.ProcessingPass; import org.enso.compiler.core.ir.Warning; import org.enso.compiler.core.ir.expression.Application; import org.enso.compiler.core.ir.module.scope.definition.Method; -import org.enso.compiler.data.CompilerConfig; -import org.enso.compiler.pass.PassConfiguration; -import org.enso.compiler.pass.PassManager; import org.enso.compiler.pass.analyse.types.InferredType; -import org.enso.compiler.pass.analyse.types.TypeInference; +import org.enso.compiler.pass.analyse.types.TypeInferencePropagation; import org.enso.compiler.pass.analyse.types.TypeRepresentation; -import org.enso.pkg.QualifiedName; +import org.enso.test.utils.ContextUtils; +import org.enso.test.utils.ModuleUtils; +import org.enso.test.utils.ProjectUtils; import org.graalvm.polyglot.Source; import org.junit.Ignore; import org.junit.Test; import scala.Option; -import scala.collection.immutable.Seq; -import scala.collection.immutable.Seq$; -import scala.jdk.javaapi.CollectionConverters; -public class TypeInferenceTest extends CompilerTests { - @Ignore("TODO resolving global methods") +public class TypeInferenceTest extends StaticAnalysisTest { @Test public void zeroAryCheck() throws Exception { final URI uri = new URI("memory://zeroAryModuleMethodCheck.enso"); @@ -60,12 +57,10 @@ public void zeroAryCheck() throws Exception { .buildLiteral(); Module module = compile(src); - Method foo = findStaticMethod(module, "foo"); - var x = findAssignment(foo.body(), "x"); - assertAtomType("zeroAryModuleMethodCheck.My_Type", x.expression()); + Method foo = ModuleUtils.findStaticMethod(module, "foo"); + assertAtomType("zeroAryModuleMethodCheck.My_Type", ModuleUtils.findAssignment(foo.body(), "x")); } - @Ignore("TODO resolution of global function application") @Test public void functionReturnCheck() throws Exception { final URI uri = new URI("memory://functionReturnCheck.enso"); @@ -88,12 +83,11 @@ public void functionReturnCheck() throws Exception { .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); - var b = findAssignment(foo.body(), "b"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); String myType = "functionReturnCheck.My_Type"; // The result of `add a z` should be `My_Type` as guaranteed by the return type check of `add`. - assertAtomType(myType, b.expression()); + assertAtomType(myType, ModuleUtils.findAssignment(foo.body(), "b")); } @Test @@ -107,13 +101,13 @@ public void argChecks() throws Exception { Value v f1 (x1 : My_Type) = - y1 = x1 - My_Type.Value (y2.v + y2.v) + y1 = x1 + My_Type.Value (y1.v + y1.v) f2 : My_Type -> My_Type f2 x2 = - y2 = x2 - My_Type.Value (y2.v + y2.v) + y2 = x2 + My_Type.Value (y2.v + y2.v) f3 (x3 : My_Type) -> My_Type = My_Type.Value (x3.v + x3.v) """, @@ -124,17 +118,17 @@ public void argChecks() throws Exception { var module = compile(src); var myType = "argChecks.My_Type"; - var f1 = findStaticMethod(module, "f1"); - var f2 = findStaticMethod(module, "f2"); - var f3 = findStaticMethod(module, "f3"); + var f1 = ModuleUtils.findStaticMethod(module, "f1"); + var f2 = ModuleUtils.findStaticMethod(module, "f2"); + var f3 = ModuleUtils.findStaticMethod(module, "f3"); - assertAtomType(myType, findAssignment(f1, "y1")); - assertNoInferredType(findAssignment(f2, "y2")); + assertAtomType(myType, ModuleUtils.findAssignment(f1, "y1")); + assertNoInferredType(ModuleUtils.findAssignment(f2, "y2")); - assertEquals("(My_Type -> My_Type)", getInferredType(f1).toString()); + assertEquals("My_Type -> My_Type", getInferredType(f1).toString()); // f2 gets argument as Any, because the doc-signature is not checked - assertEquals("(Any -> My_Type)", getInferredType(f2).toString()); - assertEquals("(My_Type -> My_Type)", getInferredType(f3).toString()); + assertEquals("Any -> My_Type", getInferredType(f2).toString()); + assertEquals("My_Type -> My_Type", getInferredType(f3).toString()); } @Test @@ -156,10 +150,10 @@ public void ascribedExpressions() throws Exception { .buildLiteral(); Module module = compile(src); - Method f = findStaticMethod(module, "f"); + Method f = ModuleUtils.findStaticMethod(module, "f"); String myType = "ascribedExpressions.My_Type"; - assertAtomType(myType, findAssignment(f.body(), "y")); + assertAtomType(myType, ModuleUtils.findAssignment(f.body(), "y")); } @Test @@ -183,9 +177,9 @@ public void advancedAscribedExpressions() throws Exception { .buildLiteral(); Module module = compile(src); - Method f = findStaticMethod(module, "f"); + Method f = ModuleUtils.findStaticMethod(module, "f"); - var y1Type = getInferredType(findAssignment(f.body(), "y1")); + var y1Type = getInferredType(ModuleUtils.findAssignment(f.body(), "y1")); if (y1Type instanceof TypeRepresentation.SumType sumType) { var gotSet = new HashSet<>(sumType.types().stream().map(TypeRepresentation::toString).toList()); @@ -194,7 +188,7 @@ public void advancedAscribedExpressions() throws Exception { fail("y1 should be a sum type, but got " + y1Type); } - var y2Type = getInferredType(findAssignment(f.body(), "y2")); + var y2Type = getInferredType(ModuleUtils.findAssignment(f.body(), "y2")); if (y2Type instanceof TypeRepresentation.IntersectionType intersectionType) { var gotSet = new HashSet<>( @@ -226,7 +220,7 @@ public void ascribedFunctionType() throws Exception { .buildLiteral(); Module module = compile(src); - Method f = findStaticMethod(module, "f"); + Method f = ModuleUtils.findStaticMethod(module, "f"); // Here we will only know that both f1 and f2 are Any -> Any - because the ascribed check only // really performs a @@ -235,8 +229,10 @@ public void ascribedFunctionType() throws Exception { // unfortunately. TypeRepresentation primitiveFunctionType = new TypeRepresentation.ArrowType(TypeRepresentation.ANY, TypeRepresentation.ANY); - assertEquals(primitiveFunctionType, getInferredType(findAssignment(f.body(), "f1"))); - assertEquals(primitiveFunctionType, getInferredType(findAssignment(f.body(), "f2"))); + assertEquals( + primitiveFunctionType, getInferredType(ModuleUtils.findAssignment(f.body(), "f1"))); + assertEquals( + primitiveFunctionType, getInferredType(ModuleUtils.findAssignment(f.body(), "f2"))); } @Test @@ -258,12 +254,12 @@ public void literals() throws Exception { .buildLiteral(); Module module = compile(src); - Method f = findStaticMethod(module, "f"); + Method f = ModuleUtils.findStaticMethod(module, "f"); - assertAtomType("Standard.Base.Data.Numbers.Integer", findAssignment(f, "x")); - assertAtomType("Standard.Base.Data.Text.Text", findAssignment(f, "y")); - assertAtomType("Standard.Base.Data.Numbers.Float", findAssignment(f, "z")); - assertAtomType("Standard.Base.Data.Vector.Vector", findAssignment(f, "w")); + assertAtomType("Standard.Base.Data.Numbers.Integer", ModuleUtils.findAssignment(f, "x")); + assertAtomType("Standard.Base.Data.Text.Text", ModuleUtils.findAssignment(f, "y")); + assertAtomType("Standard.Base.Data.Numbers.Float", ModuleUtils.findAssignment(f, "z")); + assertAtomType("Standard.Base.Data.Vector.Vector", ModuleUtils.findAssignment(f, "w")); } @Test @@ -286,11 +282,11 @@ public void bindingsFlow() throws Exception { .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); var myType = "bindingsFlow.My_Type"; - assertAtomType(myType, findAssignment(foo, "w")); + assertAtomType(myType, ModuleUtils.findAssignment(foo, "w")); } @Test @@ -312,15 +308,15 @@ public void checkedArgumentTypes() throws Exception { .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); var myType = "checkedArgumentTypes.My_Type"; // Type from argument - assertAtomType(myType, findAssignment(foo, "y1")); + assertAtomType(myType, ModuleUtils.findAssignment(foo, "y1")); // No type - assertNoInferredType(findAssignment(foo, "y2")); + assertNoInferredType(ModuleUtils.findAssignment(foo, "y2")); } @Test @@ -344,13 +340,13 @@ public void innerFunctionType() throws Exception { .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); - var f1Type = getInferredType(findAssignment(foo, "f1")); - assertEquals("(My_Type -> (My_Type -> My_Type))", f1Type.toString()); + var f1Type = getInferredType(ModuleUtils.findAssignment(foo, "f1")); + assertEquals("My_Type -> (My_Type -> My_Type)", f1Type.toString()); // and result of application is typed as the return type: - assertAtomType("innerFunctionType.My_Type", findAssignment(foo, "y")); + assertAtomType("innerFunctionType.My_Type", ModuleUtils.findAssignment(foo, "y")); } @Test @@ -372,10 +368,10 @@ public void zeroArgConstructor() throws Exception { .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); var myType = "zeroArgConstructor.My_Type"; - assertAtomType(myType, findAssignment(foo, "x")); + assertAtomType(myType, ModuleUtils.findAssignment(foo, "x")); } @Test @@ -396,10 +392,40 @@ public void multiArgConstructor() throws Exception { .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); var myType = "multiArgConstructor.My_Type"; - assertAtomType(myType, findAssignment(foo, "x")); + assertAtomType(myType, ModuleUtils.findAssignment(foo, "x")); + } + + @Test + public void nonexistentConstructor() throws Exception { + final URI uri = new URI("memory://nonexistentConstructor.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + type My_Type + Value x y z + foo = + x = My_Type.Non_Existent 1 + x + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = compile(src); + var foo = ModuleUtils.findStaticMethod(module, "foo"); + var x = ModuleUtils.findAssignment(foo, "x"); + + assertNoInferredType(x); + assertEquals( + List.of( + new Warning.NoSuchMethod( + x.expression().identifiedLocation(), + "constructor `Non_Existent` on type (type My_Type)")), + ModuleUtils.getImmediateDiagnostics(x.expression())); } @Test @@ -427,7 +453,7 @@ public void constructorWithDefaults() throws Exception { .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); var myType = "constructorWithDefaults.My_Type"; @@ -436,21 +462,24 @@ public void constructorWithDefaults() throws Exception { // Before that is working, we just ensure we did not infer any 'unexpected' type for the // results. // assertAtomType(myType, findAssignment(foo, "x1")); - assertNoInferredType(findAssignment(foo, "x1")); + assertNoInferredType(ModuleUtils.findAssignment(foo, "x1")); // assertAtomType(myType, findAssignment(foo, "x2")); - assertNoInferredType(findAssignment(foo, "x2")); + assertNoInferredType(ModuleUtils.findAssignment(foo, "x2")); // assertAtomType(myType, findAssignment(foo, "x3")); - assertNoInferredType(findAssignment(foo, "x3")); + assertNoInferredType(ModuleUtils.findAssignment(foo, "x3")); - assertNotEquals(Optional.of(myType), getInferredTypeOption(findAssignment(foo, "x4"))); - assertNotEquals(Optional.of(myType), getInferredTypeOption(findAssignment(foo, "x5"))); + assertNotEquals( + Optional.of(myType), getInferredTypeOption(ModuleUtils.findAssignment(foo, "x4"))); + assertNotEquals( + Optional.of(myType), getInferredTypeOption(ModuleUtils.findAssignment(foo, "x5"))); // assertAtomType(myType, findAssignment(foo, "x6")); - assertNoInferredType(findAssignment(foo, "x6")); + assertNoInferredType(ModuleUtils.findAssignment(foo, "x6")); - assertNotEquals(Optional.of(myType), getInferredTypeOption(findAssignment(foo, "x7"))); + assertNotEquals( + Optional.of(myType), getInferredTypeOption(ModuleUtils.findAssignment(foo, "x7"))); } @Ignore("TODO: ifte") @@ -470,8 +499,8 @@ public void commonIfThenElse() throws Exception { .buildLiteral(); var module = compile(src); - var f = findStaticMethod(module, "f"); - assertAtomType("Standard.Base.Data.Numbers.Integer", findAssignment(f, "y")); + var f = ModuleUtils.findStaticMethod(module, "f"); + assertAtomType("Standard.Base.Data.Numbers.Integer", ModuleUtils.findAssignment(f, "y")); } @Test @@ -496,8 +525,8 @@ public void commonCase() throws Exception { var module = compile(src); var myType = "commonCase.My_Type"; - var f = findStaticMethod(module, "f"); - assertAtomType(myType, findAssignment(f, "y")); + var f = ModuleUtils.findStaticMethod(module, "f"); + assertAtomType(myType, ModuleUtils.findAssignment(f, "y")); } @Test @@ -521,8 +550,8 @@ public void inferBoundsFromCaseAlias() throws Exception { var module = compile(src); var myType = "inferBoundsFromCaseAlias.My_Type"; - var f = findStaticMethod(module, "f"); - assertAtomType(myType, findAssignment(f, "y")); + var f = ModuleUtils.findStaticMethod(module, "f"); + assertAtomType(myType, ModuleUtils.findAssignment(f, "y")); } /** @@ -530,7 +559,7 @@ public void inferBoundsFromCaseAlias() throws Exception { * in one branch. We will need to ensure that we duplicate the local scopes in each branch to * avoid bad sharing. */ - @Ignore("TODO") + @Ignore("TODO for much much later: equality bounds in case") @Test public void inferEqualityBoundsFromCase() throws Exception { final URI uri = new URI("memory://inferEqualityBoundsFromCase.enso"); @@ -552,11 +581,11 @@ public void inferEqualityBoundsFromCase() throws Exception { var module = compile(src); var myType = "inferEqualityBoundsFromCase.My_Type"; - var f = findStaticMethod(module, "f"); - assertAtomType(myType, findAssignment(f, "y")); + var f = ModuleUtils.findStaticMethod(module, "f"); + assertAtomType(myType, ModuleUtils.findAssignment(f, "y")); } - @Ignore("TODO") + @Ignore("TODO for much much later: equality bounds in case") @Test public void inferEqualityBoundsFromCaseLiteral() throws Exception { final URI uri = new URI("memory://inferEqualityBoundsFromCaseLiteral.enso"); @@ -575,11 +604,11 @@ public void inferEqualityBoundsFromCaseLiteral() throws Exception { .buildLiteral(); var module = compile(src); - var f = findStaticMethod(module, "f"); - assertSumType(findAssignment(f, "y"), "Integer", "Text"); + var f = ModuleUtils.findStaticMethod(module, "f"); + assertSumType(ModuleUtils.findAssignment(f, "y"), "Integer", "Text"); } - @Ignore("TODO") + @Ignore("TODO for much much later: equality bounds in case") @Test public void inferEqualityBoundsFromCaseEdgeCase() throws Exception { // This test ensures that the equality bound from _:Other_Type is only applicable in its branch @@ -605,8 +634,8 @@ public void inferEqualityBoundsFromCaseEdgeCase() throws Exception { var module = compile(src); var myType = "inferEqualityBoundsFromCaseEdgeCase.My_Type"; - var f = findStaticMethod(module, "f"); - assertAtomType(myType, findAssignment(f, "y")); + var f = ModuleUtils.findStaticMethod(module, "f"); + assertAtomType(myType, ModuleUtils.findAssignment(f, "y")); } @Test @@ -631,11 +660,11 @@ public void sumTypeFromCase() throws Exception { .buildLiteral(); var module = compile(src); - var f = findStaticMethod(module, "f"); - assertSumType(findAssignment(f, "y"), "My_Type", "Other_Type"); + var f = ModuleUtils.findStaticMethod(module, "f"); + assertSumType(ModuleUtils.findAssignment(f, "y"), "My_Type", "Other_Type"); } - @Ignore + @Ignore("TODO: ifte") @Test public void sumTypeFromIf() throws Exception { final URI uri = new URI("memory://sumTypeFromIf.enso"); @@ -652,11 +681,11 @@ public void sumTypeFromIf() throws Exception { .buildLiteral(); var module = compile(src); - var f = findStaticMethod(module, "f"); - assertSumType(findAssignment(f, "y"), "Text", "Integer"); + var f = ModuleUtils.findStaticMethod(module, "f"); + assertSumType(ModuleUtils.findAssignment(f, "y"), "Text", "Integer"); } - @Ignore + @Ignore("TODO: ifte") @Test public void sumTypeFromIfWithoutElse() throws Exception { final URI uri = new URI("memory://sumTypeFromIf.enso"); @@ -673,8 +702,8 @@ public void sumTypeFromIfWithoutElse() throws Exception { .buildLiteral(); var module = compile(src); - var f = findStaticMethod(module, "f"); - assertSumType(findAssignment(f, "y"), "Text", "Nothing"); + var f = ModuleUtils.findStaticMethod(module, "f"); + assertSumType(ModuleUtils.findAssignment(f, "y"), "Text", "Nothing"); } @Test @@ -706,18 +735,20 @@ member_method self (x : My_Type) = var module = compile(src); var myType = "typeInferenceWorksInsideMemberMethods.My_Type"; - var staticMethod = findMemberMethod(module, "My_Type", "static_method"); - assertAtomType(myType, findAssignment(staticMethod, "y")); - assertAtomType(myType, findAssignment(staticMethod, "z")); - assertAtomType("Standard.Base.Data.Numbers.Integer", findAssignment(staticMethod, "w")); - - var memberMethod = findMemberMethod(module, "My_Type", "member_method"); - assertAtomType(myType, findAssignment(memberMethod, "y")); - assertAtomType(myType, findAssignment(memberMethod, "z")); - assertAtomType("Standard.Base.Data.Numbers.Integer", findAssignment(memberMethod, "w")); + var staticMethod = ModuleUtils.findMemberMethod(module, "My_Type", "static_method"); + assertAtomType(myType, ModuleUtils.findAssignment(staticMethod, "y")); + assertAtomType(myType, ModuleUtils.findAssignment(staticMethod, "z")); + assertAtomType( + "Standard.Base.Data.Numbers.Integer", ModuleUtils.findAssignment(staticMethod, "w")); + + var memberMethod = ModuleUtils.findMemberMethod(module, "My_Type", "member_method"); + assertAtomType(myType, ModuleUtils.findAssignment(memberMethod, "y")); + assertAtomType(myType, ModuleUtils.findAssignment(memberMethod, "z")); + assertAtomType( + "Standard.Base.Data.Numbers.Integer", ModuleUtils.findAssignment(memberMethod, "w")); } - @Ignore("TODO") + @Ignore("TODO: self resolution") @Test public void typeInferenceOfSelf() throws Exception { final URI uri = new URI("memory://typeInferenceOfSelf.enso"); @@ -737,9 +768,9 @@ public void typeInferenceOfSelf() throws Exception { .buildLiteral(); var module = compile(src); - var f = findMemberMethod(module, "My_Type", "member_method"); + var f = ModuleUtils.findMemberMethod(module, "My_Type", "member_method"); var myType = "typeInferenceOfSelf.My_Type"; - assertAtomType(myType, findAssignment(f, "y")); + assertAtomType(myType, ModuleUtils.findAssignment(f, "y")); } @Test @@ -761,23 +792,23 @@ public void notInvokable() throws Exception { .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); - var x1 = findAssignment(foo, "x1"); + var x1 = ModuleUtils.findAssignment(foo, "x1"); assertEquals( List.of(new Warning.NotInvokable(x1.expression().identifiedLocation(), "Integer")), - getImmediateDiagnostics(x1.expression())); + ModuleUtils.getImmediateDiagnostics(x1.expression())); - var x2 = findAssignment(foo, "x2"); + var x2 = ModuleUtils.findAssignment(foo, "x2"); assertEquals( List.of(new Warning.NotInvokable(x2.expression().identifiedLocation(), "Text")), - getImmediateDiagnostics(x2.expression())); + ModuleUtils.getImmediateDiagnostics(x2.expression())); - var x3 = findAssignment(foo, "x3"); + var x3 = ModuleUtils.findAssignment(foo, "x3"); assertEquals( "x3 should not contain any warnings", List.of(), - getDescendantsDiagnostics(x3.expression())); + ModuleUtils.getDescendantsDiagnostics(x3.expression())); } /** @@ -806,7 +837,7 @@ public void noErrorInParametricTypeSignatures() throws Exception { .buildLiteral(); var module = compile(src); - assertEquals(List.of(), getDescendantsDiagnostics(module)); + assertEquals(List.of(), ModuleUtils.getDescendantsDiagnostics(module)); } @Ignore("We cannot report type errors until we check there are no Conversions") @@ -831,12 +862,12 @@ public void typeErrorFromAscription() throws Exception { .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); - var y = findAssignment(foo, "y"); + var y = ModuleUtils.findAssignment(foo, "y"); var typeError = new Warning.TypeMismatch(y.expression().identifiedLocation(), "Other_Type", "My_Type"); - assertEquals(List.of(typeError), getDescendantsDiagnostics(y.expression())); + assertEquals(List.of(typeError), ModuleUtils.getDescendantsDiagnostics(y.expression())); } @Test @@ -861,13 +892,13 @@ public void noTypeErrorIfConversionExists() throws Exception { .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); - var y = findAssignment(foo, "y"); + var y = ModuleUtils.findAssignment(foo, "y"); assertEquals( "valid conversion should ensure there is no type error", List.of(), - getDescendantsDiagnostics(y.expression())); + ModuleUtils.getDescendantsDiagnostics(y.expression())); } @Ignore("We cannot report type errors until we check there are no Conversions") @@ -893,18 +924,18 @@ public void typeErrorFunctionToObject() throws Exception { .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); - var y = findAssignment(foo, "y"); + var y = ModuleUtils.findAssignment(foo, "y"); var typeError1 = new Warning.TypeMismatch(y.expression().identifiedLocation(), "My_Type", "(Any -> Any)"); - assertEquals(List.of(typeError1), getDescendantsDiagnostics(y.expression())); + assertEquals(List.of(typeError1), ModuleUtils.getDescendantsDiagnostics(y.expression())); - var z = findAssignment(foo, "z"); + var z = ModuleUtils.findAssignment(foo, "z"); var typeError2 = new Warning.TypeMismatch( - z.expression().identifiedLocation(), "My_Type", "(My_Type -> My_Type)"); - assertEquals(List.of(typeError2), getDescendantsDiagnostics(z.expression())); + z.expression().identifiedLocation(), "My_Type", "My_Type -> My_Type"); + assertEquals(List.of(typeError2), ModuleUtils.getDescendantsDiagnostics(z.expression())); } @Ignore("We cannot report type errors until we check there are no Conversions") @@ -930,9 +961,9 @@ public void typeErrorInLocalCall() throws Exception { .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); - var z = findAssignment(foo, "z"); + var z = ModuleUtils.findAssignment(foo, "z"); var arg = switch (z.expression()) { case Application.Prefix app -> app.arguments().head(); @@ -940,7 +971,7 @@ public void typeErrorInLocalCall() throws Exception { "Expected " + z.showCode() + " to be an application expression."); }; var typeError = new Warning.TypeMismatch(arg.identifiedLocation(), "Other_Type", "My_Type"); - assertEquals(List.of(typeError), getImmediateDiagnostics(arg)); + assertEquals(List.of(typeError), ModuleUtils.getImmediateDiagnostics(arg)); } @Ignore("We cannot report type errors until we check there are no Conversions") @@ -962,12 +993,12 @@ public void typeErrorInReturn() throws Exception { .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); - var x = findAssignment(foo, "x"); + var x = ModuleUtils.findAssignment(foo, "x"); var typeError = new Warning.TypeMismatch(x.expression().identifiedLocation(), "My_Type", "Integer"); - assertEquals(List.of(typeError), getDescendantsDiagnostics(x.expression())); + assertEquals(List.of(typeError), ModuleUtils.getDescendantsDiagnostics(x.expression())); } @Test @@ -991,24 +1022,27 @@ public void noTypeErrorIfUnsure() throws Exception { .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); - var y = findAssignment(foo, "y"); + var y = ModuleUtils.findAssignment(foo, "y"); assertEquals( - "y should not contain any warnings", List.of(), getDescendantsDiagnostics(y.expression())); + "y should not contain any warnings", + List.of(), + ModuleUtils.getDescendantsDiagnostics(y.expression())); - var z = findAssignment(foo, "z"); + var z = ModuleUtils.findAssignment(foo, "z"); assertEquals( - "z should not contain any warnings", List.of(), getDescendantsDiagnostics(z.expression())); + "z should not contain any warnings", + List.of(), + ModuleUtils.getDescendantsDiagnostics(z.expression())); - var baz = findAssignment(foo, "baz"); + var baz = ModuleUtils.findAssignment(foo, "baz"); assertEquals( "baz should not contain any warnings", List.of(), - getDescendantsDiagnostics(baz.expression())); + ModuleUtils.getDescendantsDiagnostics(baz.expression())); } - @Ignore("TODO") @Test public void globalMethodTypes() throws Exception { final URI uri = new URI("memory://globalMethodTypes.enso"); @@ -1019,82 +1053,524 @@ public void globalMethodTypes() throws Exception { type My_Type Value v - lit = 42 - ctor = My_Type.Value 42 const -> My_Type = My_Type.Value 23 - check (x : My_Type) = x + check (x : My_Type) -> My_Type = x foo = - x1 = lit - x2 = ctor - x3 = const - x4 = check - x5 = check const - [x1, x2, x3, x4, x5] + x1 = const + x2 = check + x3 = check const + [x1, x2, x3] """, uri.getAuthority()) .uri(uri) .buildLiteral(); var module = compile(src); - var foo = findStaticMethod(module, "foo"); + var foo = ModuleUtils.findStaticMethod(module, "foo"); var myType = "globalMethodTypes.My_Type"; - assertAtomType("Standard.Base.Data.Numbers.Integer", findAssignment(foo, "x1")); - assertAtomType(myType, findAssignment(foo, "x2")); - assertAtomType(myType, findAssignment(foo, "x3")); - assertEquals("My_Type -> My_Type", getInferredType(findAssignment(foo, "x4")).toString()); - assertAtomType(myType, findAssignment(foo, "x5")); + assertAtomType(myType, ModuleUtils.findAssignment(foo, "x1")); + assertEquals( + "My_Type -> My_Type", getInferredType(ModuleUtils.findAssignment(foo, "x2")).toString()); + assertAtomType(myType, ModuleUtils.findAssignment(foo, "x3")); } - private List getImmediateDiagnostics(IR ir) { - return CollectionConverters.asJava(ir.getDiagnostics().toList()); + @Test + public void memberMethodCalls() throws Exception { + final URI uri = new URI("memory://memberMethodCalls.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + type My_Type + Value v + + zero_arg self -> My_Type = My_Type.Value [self.v] + one_arg self (x : My_Type) -> My_Type = My_Type.Value [self.v, x.v] + + static_zero -> My_Type = My_Type.Value 42 + static_one (x : My_Type) -> My_Type = My_Type.Value [x.v, 1] + + My_Type.extension_method self -> My_Type = My_Type.Value [self.v, 2] + + foo = + inst = My_Type.Value 23 + x1 = inst.zero_arg + x2 = inst.one_arg inst + x3 = My_Type.static_zero + x4 = My_Type.static_one inst + + # And calling member methods through static syntax: + x5 = My_Type.zero_arg inst + x6 = My_Type.one_arg inst + + # And extension methods + x7 = inst.extension_method + x8 = My_Type.extension_method inst + [x1, x2, x3, x4, x5, x6, x7, x8] + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = compile(src); + var foo = ModuleUtils.findStaticMethod(module, "foo"); + + var myType = "memberMethodCalls.My_Type"; + + assertAtomType(myType, ModuleUtils.findAssignment(foo, "inst")); + assertAtomType(myType, ModuleUtils.findAssignment(foo, "x1")); + assertAtomType(myType, ModuleUtils.findAssignment(foo, "x2")); + assertAtomType(myType, ModuleUtils.findAssignment(foo, "x3")); + assertAtomType(myType, ModuleUtils.findAssignment(foo, "x4")); + assertAtomType(myType, ModuleUtils.findAssignment(foo, "x5")); + + // The function in x6 was not fully applied - still expecting 1 arg: + assertEquals( + "My_Type -> My_Type", getInferredType(ModuleUtils.findAssignment(foo, "x6")).toString()); + + assertAtomType(myType, ModuleUtils.findAssignment(foo, "x7")); + assertAtomType(myType, ModuleUtils.findAssignment(foo, "x8")); } - private List getDescendantsDiagnostics(IR ir) { - return CollectionConverters.asJava( - ir.preorder().flatMap(node -> node.getDiagnostics().toList())); + @Ignore( + "TODO: error can only be reported when we can rule out there is no Other_Type -> My_Type" + + " conversion") + @Test + public void staticCallWithWrongType() throws Exception { + final URI uri = new URI("memory://staticCallWithWrongType.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + type My_Type + Value v + + member_method self = [self.v] + + type Other_Type + Constructor v + + member_method = [self.v, self.v] + + foo = + other = Other_Type.Constructor 44 + x1 = My_Type.member_method other + x1 + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = compile(src); + var foo = ModuleUtils.findStaticMethod(module, "foo"); + + var x1 = ModuleUtils.findAssignment(foo, "x1"); + var typeError = + new Warning.TypeMismatch(x1.expression().identifiedLocation(), "My_Type", "Other_Type"); + assertEquals(List.of(typeError), ModuleUtils.getDescendantsDiagnostics(x1.expression())); } - private Method findStaticMethod(Module module, String name) { - var option = - module - .bindings() - .find( - (def) -> - (def instanceof Method binding) - && binding.methodReference().typePointer().isEmpty() - && binding.methodReference().methodName().name().equals(name)); - - assertTrue("The method " + name + " should exist within the IR.", option.isDefined()); - return (Method) option.get(); + @Test + public void callingFieldGetters() throws Exception { + final URI uri = new URI("memory://callingFieldGetters.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + type My_Type + Constructor_1 (field_a : Typ_X) (field_b : Typ_Y) + Constructor_2 (field_b : Typ_Z) + Constructor_3 (field_c : Typ_Z) + Constructor_4 (field_c : Typ_Z) + Constructor_5 field_d + + type Typ_X + type Typ_Y + type Typ_Z + + foo (instance : My_Type) = + x_a = instance.field_a + x_b = instance.field_b + x_c = instance.field_c + x_d = instance.field_d + [x_a, x_b, x_c, x_d] + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = compile(src); + var foo = ModuleUtils.findStaticMethod(module, "foo"); + + assertAtomType("callingFieldGetters.Typ_X", ModuleUtils.findAssignment(foo, "x_a")); + // We don't know which constructor was used, so if the field appears in many constructors, it + // resolves to a sum type + assertSumType(ModuleUtils.findAssignment(foo, "x_b"), "Typ_Y", "Typ_Z"); + + // We have two constructors with field `field_c`, but they have the same type so the sum type + // should have been simplified + assertAtomType("callingFieldGetters.Typ_Z", ModuleUtils.findAssignment(foo, "x_c")); + + // We shouldn't get a No_Such_Method error on a field with no type ascription: + var x_d = ModuleUtils.findAssignment(foo, "x_d"); + assertEquals( + "Field access should not yield any warnings", + List.of(), + ModuleUtils.getDescendantsDiagnostics(x_d)); } - private Method findMemberMethod(Module module, String typeName, String name) { - var option = - module - .bindings() - .find( - (def) -> - (def instanceof Method binding) - && binding.methodReference().typePointer().isDefined() - && binding.methodReference().typePointer().get().name().equals(typeName) - && binding.methodReference().methodName().name().equals(name)); - - assertTrue("The method " + name + " should exist within the IR.", option.isDefined()); - return (Method) option.get(); + @Test + public void noSuchMethodStaticCheck() throws Exception { + final URI uri = new URI("memory://noSuchMethodStaticCheck.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + import Standard.Base.Any.Any + + type My_Type + Value v + + method_one self = 42 + static_method = 44 + + foo = + inst = My_Type.Value 23 + x1 = inst.method_one + x2 = inst.method_two + x3 = inst.to_text + x4 = inst.is_error + x5 = inst.static_method + [x1, x2, x3, x4, x5] + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = compile(src); + var foo = ModuleUtils.findStaticMethod(module, "foo"); + var x1 = ModuleUtils.findAssignment(foo, "x1"); + var x2 = ModuleUtils.findAssignment(foo, "x2"); + var x3 = ModuleUtils.findAssignment(foo, "x3"); + var x4 = ModuleUtils.findAssignment(foo, "x4"); + var x5 = ModuleUtils.findAssignment(foo, "x5"); + + // member method is defined + assertEquals(List.of(), ModuleUtils.getDescendantsDiagnostics(x1.expression())); + + // this method is not found + assertEquals( + List.of( + new Warning.NoSuchMethod( + x2.expression().identifiedLocation(), + "member method `method_two` on type My_Type")), + ModuleUtils.getImmediateDiagnostics(x2.expression())); + + // delegating to Any + assertEquals(List.of(), ModuleUtils.getDescendantsDiagnostics(x3.expression())); + assertEquals(List.of(), ModuleUtils.getDescendantsDiagnostics(x4.expression())); + + // calling a static method on an instance _does not work_, so we get a warning telling there's + // no such _member_ method + assertEquals( + List.of( + new Warning.NoSuchMethod( + x5.expression().identifiedLocation(), + "member method `static_method` on type My_Type")), + ModuleUtils.getImmediateDiagnostics(x5.expression())); } - private Expression.Binding findAssignment(IR ir, String name) { - var option = - ir.preorder() - .find( - (node) -> - (node instanceof Expression.Binding binding) - && binding.name().name().equals(name)); - assertTrue("The binding `" + name + " = ...` should exist within the IR.", option.isDefined()); - return (Expression.Binding) option.get(); + @Test + public void alwaysKnowsMethodsOfAny() throws Exception { + final URI uri = new URI("memory://alwaysKnowsMethodsOfAny.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + type My_Type + Value v + + foo x = + txt1 = x.to_text + txt2 = 42.to_text + txt3 = (My_Type.Value 1).to_text + + bool = (x == x) + [txt1, txt2, txt3, bool] + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = compile(src); + var foo = ModuleUtils.findStaticMethod(module, "foo"); + + assertAtomType("Standard.Base.Data.Text.Text", ModuleUtils.findAssignment(foo, "txt1")); + assertAtomType("Standard.Base.Data.Text.Text", ModuleUtils.findAssignment(foo, "txt2")); + assertAtomType("Standard.Base.Data.Text.Text", ModuleUtils.findAssignment(foo, "txt3")); + + assertAtomType("Standard.Base.Data.Boolean.Boolean", ModuleUtils.findAssignment(foo, "bool")); + } + + @Ignore("TODO: self resolution") + @Test + public void noSuchMethodOnSelf() throws Exception { + final URI uri = new URI("memory://noSuchMethodOnSelf.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + type My_Type + Value v + + method_one self = 42 + method_two self = + x1 = self.method_one + x2 = self.non_existent_method + [x1, x2] + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = compile(src); + var method_two = ModuleUtils.findMemberMethod(module, "My_Type", "method_two"); + var x1 = ModuleUtils.findAssignment(method_two, "x1"); + var x2 = ModuleUtils.findAssignment(method_two, "x2"); + + // member method is defined + assertEquals(List.of(), ModuleUtils.getDescendantsDiagnostics(x1.expression())); + + // this method is not found + assertEquals( + List.of( + new Warning.NoSuchMethod( + x2.expression().identifiedLocation(), + "member method `non_existent_method` on type My_Type")), + ModuleUtils.getImmediateDiagnostics(x2.expression())); + } + + @Test + public void callingExtensionMethodDefinedElsewhere() throws Exception { + final URI uriA = new URI("memory://local.Project1.modA.enso"); + final Source srcA = + Source.newBuilder( + "enso", + """ + type My_Type + Value v + """, + uriA.getAuthority()) + .uri(uriA) + .buildLiteral(); + compile(srcA); + + final URI uriB = new URI("memory://local.Project1.modB.enso"); + final Source srcB = + Source.newBuilder( + "enso", + """ + import local.Project1.modA.My_Type + + type Typ_X + Value a + type Typ_Y + Value a + + My_Type.member self -> Typ_X = Typ_X.Value self + My_Type.static -> Typ_Y = Typ_Y.Value 32 + """, + uriB.getAuthority()) + .uri(uriB) + .buildLiteral(); + compile(srcB); + + final URI uriC = new URI("memory://local.Project1.modC.enso"); + final Source srcC = + Source.newBuilder( + "enso", + """ + import local.Project1.modA.My_Type + from local.Project1.modB import all + + foo = + inst = My_Type.Value 23 + x1 = inst.member + x2 = My_Type.static + [x1, x2] + """, + uriC.getAuthority()) + .uri(uriC) + .buildLiteral(); + var modC = compile(srcC); + var foo = ModuleUtils.findStaticMethod(modC, "foo"); + + assertAtomType("local.Project1.modB.Typ_X", ModuleUtils.findAssignment(foo, "x1")); + assertAtomType("local.Project1.modB.Typ_Y", ModuleUtils.findAssignment(foo, "x2")); + } + + @Test + public void callingReexportedExtensionMethods() throws Exception { + // Base type definition + final URI uriA = new URI("memory://local.Project1.modA.enso"); + final Source srcA = + Source.newBuilder( + "enso", + """ + type My_Type + Value v + """, + uriA.getAuthority()) + .uri(uriA) + .buildLiteral(); + compile(srcA); + + // Extension methods defined in another module + final URI uriB = new URI("memory://local.Project1.modB.enso"); + final Source srcB = + Source.newBuilder( + "enso", + """ + import local.Project1.modA.My_Type + + type Typ_X + Value a + type Typ_Y + Value a + + My_Type.member self -> Typ_X = Typ_X.Value self + My_Type.static -> Typ_Y = Typ_Y.Value 32 + """, + uriB.getAuthority()) + .uri(uriB) + .buildLiteral(); + compile(srcB); + + // Re-exports of the type and the extension method + final URI uriC = new URI("memory://local.Project1.modC.enso"); + final Source srcC = + Source.newBuilder( + "enso", + """ + export local.Project1.modA.My_Type + export local.Project1.modB.member + """, + uriC.getAuthority()) + .uri(uriC) + .buildLiteral(); + compile(srcC); + + final URI uriD = new URI("memory://local.Project1.modD.enso"); + final Source srcD = + Source.newBuilder( + "enso", + """ + from local.Project1.modC import all + + foo = + inst = My_Type.Value 23 + x1 = inst.member + x2 = My_Type.static + [x1, x2] + """, + uriD.getAuthority()) + .uri(uriD) + .buildLiteral(); + var modD = compile(srcD); + var foo = ModuleUtils.findStaticMethod(modD, "foo"); + + assertAtomType("local.Project1.modB.Typ_X", ModuleUtils.findAssignment(foo, "x1")); + assertAtomType("local.Project1.modB.Typ_Y", ModuleUtils.findAssignment(foo, "x2")); + } + + @Test + public void resolveImportedConstructor() throws Exception { + final URI uri = new URI("memory://local.Project1.modA.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + from project.modA.My_Type import My_Constructor + + type My_Type + My_Constructor v + + foo = + x1 = My_Constructor 1 + x1 + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = compile(src); + var foo = ModuleUtils.findStaticMethod(module, "foo"); + var x1 = ModuleUtils.findAssignment(foo, "x1"); + assertAtomType("local.Project1.modA.My_Type", x1); + } + + @Ignore("TODO: for later") + @Test + public void resolveFQNConstructor() throws Exception { + final URI uri = new URI("memory://local.Project1.modA.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + type My_Type + My_Constructor v + + foo = + x1 = local.Project1.modA.My_Type.My_Constructor 1 + x1 + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = compile(src); + var foo = ModuleUtils.findStaticMethod(module, "foo"); + var x1 = ModuleUtils.findAssignment(foo, "x1"); + assertAtomType("local.Project1.modA.My_Type", x1); + } + + @Test + public void staticTypeCheckerReportsWarningsOnProject() throws IOException { + var mainSrc = + """ + bar = + 1.non_existent_method + + main = + 42 + """; + Path projDir = Files.createTempDirectory("enso-tests"); + ProjectUtils.createProject("Proj", mainSrc, projDir); + var out = new ByteArrayOutputStream(); + var ctxBuilder = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.DISABLE_IR_CACHES, "true") + .option(RuntimeOptions.ENABLE_STATIC_ANALYSIS, "true") + .option(RuntimeOptions.STRICT_ERRORS, "true") + .currentWorkingDirectory(projDir.getParent()) + .out(out) + .err(out) + .logHandler(out); + ProjectUtils.testProjectRun( + ctxBuilder, + projDir, + res -> { + assertThat(res.isNumber(), is(true)); + assertThat(res.asInt(), is(42)); + assertThat( + out.toString(), + containsString( + "Calling member method `non_existent_method` on type Integer will result in a" + + " No_Such_Method error")); + }); } private TypeRepresentation getInferredType(IR ir) { @@ -1106,7 +1582,7 @@ private TypeRepresentation getInferredType(IR ir) { } private Optional getInferredTypeOption(IR ir) { - Option metadata = ir.passData().get(TypeInference.INSTANCE); + Option metadata = ir.passData().get(TypeInferencePropagation.INSTANCE); if (metadata.isEmpty()) { return Optional.empty(); } else { @@ -1116,7 +1592,7 @@ private Optional getInferredTypeOption(IR ir) { } private void assertNoInferredType(IR ir) { - Option metadata = ir.passData().get(TypeInference.INSTANCE); + Option metadata = ir.passData().get(TypeInferencePropagation.INSTANCE); assertTrue( "Expecting " + ir.showCode() @@ -1126,11 +1602,24 @@ private void assertNoInferredType(IR ir) { } private void assertAtomType(String fqn, IR ir) { - var type = getInferredType(ir); + var option = getInferredTypeOption(ir); + if (option.isEmpty()) { + fail( + "Expected " + + ir.showCode() + + " to have Atom type " + + fqn + + ", but no type metadata was found."); + } + + var type = option.get(); if (type instanceof TypeRepresentation.AtomType atomType) { - assertEquals(fqn, atomType.fqn().toString()); + assertEquals( + "Expected " + ir.showCode() + " to have the right atom type: ", + fqn, + atomType.fqn().toString()); } else { - fail("Expected " + ir.showCode() + " to have an AtomType, but got " + type); + fail("Expected " + ir.showCode() + " to have an Atom type " + fqn + ", but got " + type); } } @@ -1144,48 +1633,4 @@ private void assertSumType(IR ir, String... shortNames) { fail("Expected " + ir.showCode() + " to have a SumType, but got " + type); } } - - /** - * Note that this `compile` method will not run import resolution. For now we just have tests that - * do not need it, and tests that do need it are placed in {@link - * org.enso.interpreter.test.TypeInferenceConsistencyTest} which spawns the whole interpreter. - * - *

If we want to run the imports resolution here, we need to create an instance of {@link - * Compiler}, like in {@link org.enso.compiler.test.semantic.TypeSignaturesTest}, but that relies - * on spawning a Graal context anyway. If possible I think it's good to skip that so that these - * tests can be kept simple - and the more complex ones can be done in the other suite. - */ - private Module compile(Source src) { - if (src.getCharacters().toString().contains("import")) { - throw new IllegalArgumentException("This method will not work correctly with imports."); - } - - Module rawModule = parse(src.getCharacters()); - - var compilerConfig = - new CompilerConfig(false, true, true, true, false, true, false, Option.empty()); - var passes = new Passes(compilerConfig); - @SuppressWarnings("unchecked") - var passConfig = - new PassConfiguration((Seq>) Seq$.MODULE$.empty()); - PassManager passManager = new PassManager(passes.passOrdering(), passConfig); - var compilerRunner = - new CompilerRunner() { - @Override - public CompilerConfig defaultConfig() { - return compilerConfig; - } - - @Override - public void org$enso$compiler$test$CompilerRunner$_setter_$defaultConfig_$eq( - CompilerConfig x$1) {} - }; - var moduleName = QualifiedName.simpleName(src.getName().replace(".enso", "")); - ModuleContext moduleContext = - compilerRunner.buildModuleContext( - moduleName, Option.apply(new FreshNameSupply()), Option.empty(), compilerConfig, false); - Module processedModule = - compilerRunner.runPassesOnModule(rawModule, passManager, moduleContext); - return processedModule; - } } diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/TypesFromSignaturesTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/TypesFromSignaturesTest.java new file mode 100644 index 000000000000..790cdd253776 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/TypesFromSignaturesTest.java @@ -0,0 +1,188 @@ +package org.enso.compiler.test; + +import static org.enso.test.utils.ModuleUtils.findMemberMethod; +import static org.enso.test.utils.ModuleUtils.findStaticMethod; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.net.URI; +import java.net.URISyntaxException; +import org.enso.compiler.core.IR; +import org.enso.compiler.core.ir.ProcessingPass; +import org.enso.compiler.pass.analyse.types.InferredType; +import org.enso.compiler.pass.analyse.types.TypeInferenceSignatures; +import org.enso.compiler.pass.analyse.types.TypeRepresentation; +import org.graalvm.polyglot.Source; +import org.junit.Test; +import scala.Option; + +public class TypesFromSignaturesTest extends StaticAnalysisTest { + + @Test + public void simpleCheck() throws Exception { + final URI uri = new URI("memory://simpleCheck.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + type A + type B + type C + Value v + + f (x : A) (y : B) -> C = C.Value [x, y] + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = compile(src); + var f1 = findStaticMethod(module, "f"); + assertTypeRepresentation(f1, "A -> (B -> C)"); + } + + @Test + public void variousExpressions() throws Exception { + final URI uri = new URI("memory://variousExpressions.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + type A + Value + + self other = other + type B + + f1 (_ : A) -> B = 1 + f2 (x : A) -> B = x + 10 + f3 (x : A) -> B = [x] + f4 (x : A) -> B = f1 x + f5 (x : A) -> B = x + f6 (x : A) -> B = + y = x + z = y + z + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = compile(src); + assertTypeRepresentation(findStaticMethod(module, "f1"), "A -> B"); + assertTypeRepresentation(findStaticMethod(module, "f2"), "A -> B"); + assertTypeRepresentation(findStaticMethod(module, "f3"), "A -> B"); + assertTypeRepresentation(findStaticMethod(module, "f4"), "A -> B"); + assertTypeRepresentation(findStaticMethod(module, "f5"), "A -> B"); + assertTypeRepresentation(findStaticMethod(module, "f6"), "A -> B"); + } + + @Test + public void justArity() throws Exception { + final URI uri = new URI("memory://justArity.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + type A + type B + type C + Value v + + f0 = 0 + + f4 x y z w = [x, y, z, w] + + f2 : A -> B -> C + f2 x y = [x, y] + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = compile(src); + + var f0 = findStaticMethod(module, "f0"); + var f4 = findStaticMethod(module, "f4"); + var f2 = findStaticMethod(module, "f2"); + + // For 0 arguments and unknown return type we know nothing useful, so no information is + // registered. + assertNoInferredType(f0); + + // For a function without ascriptions, we can at least infer the _arity_ + // Currently that is denoted by replacing unknowns with Any. Later this may be free type + // variables. + assertTypeRepresentation(f4, "Any -> (Any -> (Any -> (Any -> Any)))"); + + // For the 'opted-out' ascription, the types are ignored, because they are not checked types. + // But we still infer arity. + assertTypeRepresentation(f2, "Any -> (Any -> Any)"); + } + + @Test + public void memberMethods() throws URISyntaxException { + final URI uri = new URI("memory://memberMethods.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + type A + static_method (x : A) -> A = x + member_method self (x : A) -> A = x + standalone_method (x : A) -> A = x + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = compile(src); + var staticMethod = findMemberMethod(module, "A", "static_method"); + var memberMethod = findMemberMethod(module, "A", "member_method"); + + assertTypeRepresentation(staticMethod, "A -> A"); + assertTypeRepresentation(memberMethod, "A -> A"); + } + + @Test + public void extensionMethods() throws URISyntaxException { + final URI uri = new URI("memory://extensionMethods.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + type A + + A.extension_static_method (x : A) -> A = x + A.extension_member_method self (x : A) -> A = x + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = compile(src); + var staticMethod = findMemberMethod(module, "A", "extension_static_method"); + var memberMethod = findMemberMethod(module, "A", "extension_member_method"); + + assertTypeRepresentation(staticMethod, "A -> A"); + assertTypeRepresentation(memberMethod, "A -> A"); + } + + private void assertTypeRepresentation(IR ir, String expected) { + TypeRepresentation inferred = getInferredType(ir); + assertEquals(expected, inferred.toString()); + } + + private TypeRepresentation getInferredType(IR ir) { + Option metadata = ir.passData().get(TypeInferenceSignatures.INSTANCE); + assertTrue( + "Expecting " + ir.showCode() + " to contain a type within metadata.", metadata.isDefined()); + InferredType inferred = (InferredType) metadata.get(); + return inferred.type(); + } + + private void assertNoInferredType(IR ir) { + Option metadata = ir.passData().get(TypeInferenceSignatures.INSTANCE); + assertTrue( + "Expecting " + ir.showCode() + " to contain no type within metadata.", metadata.isEmpty()); + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/SignatureTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/SignatureTest.java index 2c9da884f644..ab4f43705581 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/SignatureTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/SignatureTest.java @@ -1426,6 +1426,52 @@ public void returnTypeCheckProduct() throws Exception { } } + @Test + public void returnTypeCheckOptInErrorMethodsOnTypes() throws Exception { + final URI uri = new URI("memory://returnTypeCheckOptInErrorMethodsOnTypes.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + from Standard.Base import Integer + type My_Type + Value + plus_member self a b -> Integer = b+a + plus_static a b -> Integer = b+a + """, + uri.getAuthority()) + .uri(uri) + .buildLiteral(); + + var module = ctx.eval(src); + + var res1 = + module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "My_Type.Value.plus_member 1 2"); + assertEquals(3, res1.asInt()); + + var res2 = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "My_Type.plus_static 3 4"); + assertEquals(7, res2.asInt()); + + try { + var res = + module.invokeMember( + MethodNames.Module.EVAL_EXPRESSION, "My_Type.Value.plus_member 'a' 'b'"); + fail("Expecting an exception, not: " + res); + } catch (PolyglotException e) { + assertContains( + "expected the result of `plus_member` to be Integer, but got Text", e.getMessage()); + } + + try { + var res = + module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "My_Type.plus_static 'a' 'b'"); + fail("Expecting an exception, not: " + res); + } catch (PolyglotException e) { + assertContains( + "expected the result of `plus_static` to be Integer, but got Text", e.getMessage()); + } + } + static void assertTypeError(String expArg, String expType, String realType, String msg) { assertEquals( "Type error: expected " + expArg + " to be " + expType + ", but got " + realType + ".", diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/TypeInferenceConsistencyTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/TypeInferenceConsistencyTest.java index a48e6307b5bf..aed0895bd608 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/TypeInferenceConsistencyTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/TypeInferenceConsistencyTest.java @@ -272,7 +272,7 @@ private void assertNotInvokableRuntimeError(String got, PolyglotException except private static void assertContains(String exp, String msg) { if (!msg.contains(exp)) { - fail("Expecting " + msg + " to contain " + exp); + fail("Expecting '" + msg + "' to contain '" + exp + "'."); } } } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/CompilerTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/CompilerTest.scala index a81def60f616..f82944c7f4af 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/CompilerTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/CompilerTest.scala @@ -323,7 +323,7 @@ trait CompilerRunner { compilerConfig = compilerConfig ) InlineContext( - module = mc, + moduleContext = mc, freshNameSupply = freshNameSupply, passConfiguration = passConfiguration, localScope = localScope, diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala index 8342d7245f75..439ebfb6bca4 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala @@ -18,7 +18,6 @@ import org.enso.compiler.data.BindingsMap.{ import org.enso.compiler.pass.analyse.BindingAnalysis import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager} import org.enso.compiler.test.CompilerTest -import org.enso.persist.Persistance class BindingAnalysisTest extends CompilerTest { @@ -168,18 +167,15 @@ class BindingAnalysisTest extends CompilerTest { List( Argument( "a", - hasDefaultValue = false, - Persistance.Reference.none() + hasDefaultValue = false ), Argument( "b", - hasDefaultValue = false, - Persistance.Reference.none() + hasDefaultValue = false ), Argument( "c", - hasDefaultValue = false, - Persistance.Reference.none() + hasDefaultValue = false ) ), isProjectPrivate = false diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/IrPersistance.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/IrPersistance.java index d317951014c2..4132ba57a683 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/IrPersistance.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/IrPersistance.java @@ -69,6 +69,8 @@ @Persistable(clazz = Warning.WrongBuiltinMethod.class, id = 789) @Persistable(clazz = Warning.NotInvokable.class, id = 791) @Persistable(clazz = Warning.TypeMismatch.class, id = 792) +@Persistable(clazz = Warning.NoSuchMethod.class, id = 793) +@Persistable(clazz = Warning.NonUnitTypeUsedOnValueLevel.class, id = 794) @Persistable(clazz = Operator.Binary.class, id = 790) public final class IrPersistance { private IrPersistance() {} diff --git a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Warning.scala b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Warning.scala index be4ff3e4ad10..c43986b46058 100644 --- a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Warning.scala +++ b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Warning.scala @@ -75,6 +75,24 @@ object Warning { override def diagnosticKeys(): Array[Any] = Array() } + /** A warning about calling a method (or field getter) that is not defined on the given type. + * + * This warning indicates a place that will result in a No_Such_Method error in runtime. + * + * @param identifiedLocation the location of the call + * @param methodDescription the description of the method + */ + case class NoSuchMethod( + override val identifiedLocation: IdentifiedLocation, + methodDescription: String + ) extends Warning { + override def message(source: (IdentifiedLocation => String)): String = { + s"Calling $methodDescription will result in a No_Such_Method error in runtime." + } + + override def diagnosticKeys(): Array[Any] = Array() + } + /** A warning about a `@Builtin_Method` annotation placed in a method * with unexpected body. * diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ImportExportScope.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ImportExportScope.java index 2d46bd8f8949..b61482281b61 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ImportExportScope.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ImportExportScope.java @@ -41,9 +41,9 @@ public Function getExportedMethod(Type type, String name) { } } - public Function getExportedConversion(Type type, Type target) { + public Function getExportedConversion(Type target, Type source) { if (isValidType(target)) { - return module.getScope().getExportedConversion(type, target); + return module.getScope().getExportedConversion(target, source); } else { return null; } @@ -57,13 +57,9 @@ public Function getMethodForType(Type type, String methodName) { } } - public Function getConversionForType(Type target, Type type) { + public Function getConversionForType(Type target, Type source) { if (isValidType(target)) { - var result = module.getScope().getConversionsFor(target); - if (result == null) { - return null; - } - return result.get(type); + return module.getScope().getConversionFor(target, source); } else { return null; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java index 92d09901c40e..3d4efde52cd3 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java @@ -8,6 +8,7 @@ import java.util.*; import java.util.function.Supplier; import java.util.stream.Collectors; +import org.enso.compiler.common.MethodResolutionAlgorithm; import org.enso.compiler.context.CompilerContext; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.Module; @@ -89,21 +90,78 @@ public Module getModule() { */ @CompilerDirectives.TruffleBoundary public Function lookupMethodDefinition(Type type, String name) { - var definedWithAtom = type.getDefinitionScope().getMethodForType(type, name); - if (definedWithAtom != null) { - return definedWithAtom; + return methodResolutionAlgorithm.lookupMethodDefinition(this, type, name); + } + + private final RuntimeMethodResolution methodResolutionAlgorithm = new RuntimeMethodResolution(); + + private static final class RuntimeMethodResolution + extends MethodResolutionAlgorithm { + + @Override + protected Collection getImportsFromModuleScope(ModuleScope moduleScope) { + return moduleScope.getImports(); + } + + @Override + protected Collection getExportsFromModuleScope(ModuleScope moduleScope) { + return moduleScope.getExports(); + } + + @Override + protected Function getConversionFromModuleScope( + ModuleScope moduleScope, Type target, Type source) { + return moduleScope.getConversionFor(target, source); + } + + @Override + protected Function getMethodFromModuleScope( + ModuleScope moduleScope, Type type, String methodName) { + return moduleScope.getMethodForType(type, methodName); + } + + @Override + protected ModuleScope findDefinitionScope(Type type) { + return type.getDefinitionScope(); } - var definedHere = getMethodForType(type, name); - if (definedHere != null) { - return definedHere; + @Override + protected Function getMethodForTypeFromScope( + ImportExportScope scope, Type type, String methodName) { + return scope.getMethodForType(type, methodName); } - return imports.stream() - .map(scope -> scope.getExportedMethod(type, name)) - .filter(Objects::nonNull) - .findFirst() - .orElse(null); + @Override + protected Function getExportedMethodFromScope( + ImportExportScope scope, Type type, String methodName) { + return scope.getExportedMethod(type, methodName); + } + + @Override + protected Function getConversionFromScope(ImportExportScope scope, Type target, Type source) { + return scope.getConversionForType(target, source); + } + + @Override + protected Function getExportedConversionFromScope( + ImportExportScope scope, Type target, Type source) { + return scope.getExportedConversion(target, source); + } + + @Override + protected Function onMultipleDefinitionsFromImports( + String methodName, List> methodFromImports) { + assert !methodFromImports.isEmpty(); + return methodFromImports.get(0).resolutionResult(); + } + } + + public Collection getImports() { + return imports; + } + + public Collection getExports() { + return exports; } /** @@ -119,51 +177,19 @@ public Function lookupMethodDefinition(Type type, String name) { * * @param source Source type * @param target Target type - * @return The conversion method or null if not found. + * @return The conversion method or null if not found.nie */ @CompilerDirectives.TruffleBoundary public Function lookupConversionDefinition(Type source, Type target) { - Function definedWithSource = source.getDefinitionScope().getConversionsFor(target).get(source); - if (definedWithSource != null) { - return definedWithSource; - } - Function definedWithTarget = target.getDefinitionScope().getConversionsFor(target).get(source); - if (definedWithTarget != null) { - return definedWithTarget; - } - Function definedHere = getConversionsFor(target).get(source); - if (definedHere != null) { - return definedHere; - } - return imports.stream() - .map(scope -> scope.getExportedConversion(source, target)) - .filter(Objects::nonNull) - .findFirst() - .orElse(null); + return methodResolutionAlgorithm.lookupConversionDefinition(this, source, target); } Function getExportedMethod(Type type, String name) { - var here = getMethodForType(type, name); - if (here != null) { - return here; - } - return exports.stream() - .map(scope -> scope.getMethodForType(type, name)) - .filter(Objects::nonNull) - .findFirst() - .orElse(null); + return methodResolutionAlgorithm.getExportedMethod(this, type, name); } - Function getExportedConversion(Type type, Type target) { - Function here = getConversionsFor(target).get(type); - if (here != null) { - return here; - } - return exports.stream() - .map(scope -> scope.getConversionForType(target, type)) - .filter(Objects::nonNull) - .findFirst() - .orElse(null); + Function getExportedConversion(Type target, Type source) { + return methodResolutionAlgorithm.getExportedConversion(this, target, source); } public List getAllTypes(String name) { @@ -232,12 +258,13 @@ public Set getMethodsForType(Type tpe) { } } - Map getConversionsFor(Type type) { - var result = conversions.get(type); - if (result == null) { - return new LinkedHashMap<>(); + public Function getConversionFor(Type target, Type source) { + var conversionsOnType = conversions.get(target); + if (conversionsOnType == null) { + return null; } - return result; + + return conversionsOnType.get(source); } /** @@ -506,6 +533,10 @@ public ModuleScope build() { return moduleScope; } + public Type getAssociatedType() { + return associatedType; + } + public static ModuleScope.Builder fromCompilerModuleScopeBuilder( CompilerContext.ModuleScopeBuilder scopeBuilder) { return ((TruffleCompilerModuleScopeBuilder) scopeBuilder).unsafeScopeBuilder(); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java index d48cacddf02b..3c6aee846d8d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java @@ -184,10 +184,14 @@ private static Object leakContext(EnsoContext context) { @CompilerDirectives.TruffleBoundary private static Object compile(Object[] arguments, EnsoContext context) throws UnsupportedTypeException, ArityException { - boolean useGlobalCache = context.isUseGlobalCache(); boolean shouldCompileDependencies = Types.extractArguments(arguments, Boolean.class); + boolean shouldWriteCache = !context.isIrCachingDisabled(); + boolean useGlobalCache = context.isUseGlobalCache(); try { - return context.getCompiler().compile(shouldCompileDependencies, useGlobalCache).get(); + return context + .getCompiler() + .compile(shouldCompileDependencies, shouldWriteCache, useGlobalCache) + .get(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala index 9790ed9c9be1..c932c2c63bd0 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala @@ -2,13 +2,13 @@ package org.enso.interpreter.runtime import com.oracle.truffle.api.source.{Source, SourceSection} import com.oracle.truffle.api.interop.InteropLibrary -import org.enso.compiler.pass.analyse.FramePointer -import org.enso.compiler.pass.analyse.FrameVariableNames -import org.enso.compiler.context.{ - CompilerContext, - LocalScope, +import org.enso.compiler.common.{ + BuildScopeFromModuleAlgorithm, NameResolutionAlgorithm } +import org.enso.compiler.pass.analyse.FramePointer +import org.enso.compiler.pass.analyse.FrameVariableNames +import org.enso.compiler.context.{CompilerContext, LocalScope} import org.enso.compiler.core.CompilerError import org.enso.compiler.core.ConstantsNames import org.enso.compiler.core.Implicits.AsMetadata @@ -28,9 +28,6 @@ import org.enso.compiler.core.ir.{ Type => Tpe } import org.enso.compiler.core.ir.module.scope.Definition -import org.enso.compiler.core.ir.module.scope.definition -import org.enso.compiler.core.ir.module.scope.Import -import org.enso.compiler.core.ir.module.scope.imports import org.enso.compiler.core.ir.expression.{ errors, Application, @@ -186,80 +183,226 @@ class IrToTruffle( * @param module the module for which code should be generated */ private def processModule(module: Module): Unit = { - generateReExportBindings(module) - val bindingsMap = - module - .unsafeGetMetadata( - BindingAnalysis, - "No binding analysis at the point of codegen." - ) - - registerModuleExports(bindingsMap) - registerModuleImports(bindingsMap) - registerPolyglotImports(module) + val bindingsMap = module.unsafeGetMetadata( + BindingAnalysis, + "No binding analysis at the point of codegen." + ) - registerTypeDefinitions(module) - registerMethodDefinitions(module) - registerConversions(module) + // TODO [RW] perhaps later this should be moved to the BuildModuleScopeFromModule + generateReExportBindings(module) + val builderAlgorithm = new BuildModuleScopeFromModule + builderAlgorithm.processModule(module, bindingsMap) scopeBuilder.build() } - private def registerModuleExports(bindingsMap: BindingsMap): Unit = - bindingsMap.getDirectlyExportedModules.foreach { exportedMod => - val exportedRuntimeMod = exportedMod.module.module.unsafeAsModule() - scopeBuilder.addExport( - new ImportExportScope(exportedRuntimeMod) - ) + final private class BuildModuleScopeFromModule + extends BuildScopeFromModuleAlgorithm[Type, ImportExportScope] { + + override protected def registerExport( + exportScope: ImportExportScope + ): Unit = { + scopeBuilder.addExport(exportScope) } - private def registerModuleImports(bindingsMap: BindingsMap): Unit = - bindingsMap.resolvedImports.foreach { imp => - imp.targets.foreach { - case _: BindingsMap.ResolvedType => - case _: BindingsMap.ResolvedConstructor => - case _: BindingsMap.ResolvedModuleMethod => - case _: BindingsMap.ResolvedExtensionMethod => - case _: BindingsMap.ResolvedConversionMethod => - case ResolvedModule(module) => - val mod = module - .unsafeAsModule() - val scope: ImportExportScope = imp.importDef.onlyNames - .map(only => new ImportExportScope(mod, only.map(_.name).asJava)) - .getOrElse(new ImportExportScope(mod)) - scopeBuilder.addImport(scope) + override protected def registerImport( + importScope: ImportExportScope + ): Unit = { + scopeBuilder.addImport(importScope) + } + + override protected def getTypeAssociatedWithCurrentScope(): Type = + scopeAssociatedType + + override protected def processPolyglotJavaImport( + visibleName: String, + javaClassName: String + ): Unit = + scopeBuilder.registerPolyglotSymbol( + visibleName, + () => context.lookupJavaClass(javaClassName) + ) + + override protected def processConversion( + conversion: Method.Conversion + ): Unit = { + def where() = + s"conversion `${conversion.typeName.map(_.name + ".").getOrElse("")}${conversion.methodName.name}`." + val scopeInfo = rootScopeInfo(where, conversion) + + def dataflowInfo() = conversion.unsafeGetMetadata( + DataflowAnalysis, + "Method definition missing dataflow information." + ) + def frameInfo() = conversion.unsafeGetMetadata( + FramePointerAnalysis, + "Method definition missing frame information." + ) + + val toOpt = + conversion.methodReference.typePointer match { + case Some(tpePointer) => + getTypeResolution(tpePointer) + case None => + Some(scopeAssociatedType) + } + val fromOpt = getTypeResolution(conversion.sourceTypeName) + toOpt.zip(fromOpt).foreach { case (toType, fromType) => + val expressionProcessor = new ExpressionProcessor( + toType.getName ++ Constants.SCOPE_SEPARATOR ++ conversion.methodName.name, + () => scopeInfo().graph, + () => scopeInfo().graph.rootScope, + dataflowInfo, + conversion.methodName.name, + frameInfo + ) + + val function = conversion.body match { + case fn: Function => + val bodyBuilder = + new expressionProcessor.BuildFunctionBody( + conversion.methodName.name, + fn.arguments, + fn.body, + TypeCheckValueNode.single("conversion", toType), + None, + true + ) + val rootNode = MethodRootNode.build( + language, + expressionProcessor.scope, + scopeBuilder.asModuleScope(), + () => bodyBuilder.bodyNode(), + makeSection(scopeBuilder.getModule, conversion.location), + toType, + conversion.methodName.name + ) + val callTarget = rootNode.getCallTarget + val arguments = bodyBuilder.args() + val funcSchema = FunctionSchema + .newBuilder() + .argumentDefinitions(arguments: _*) + .build() + new RuntimeFunction( + callTarget, + null, + funcSchema + ) + case _ => + throw new CompilerError( + s"Conversion bodies must be functions at the point of codegen (conversion $fromType to $toType)." + ) + } + scopeBuilder.registerConversionMethod(toType, fromType, function) } } - private def registerPolyglotImports(module: Module): Unit = - module.imports.foreach { - case poly @ imports.Polyglot(i: imports.Polyglot.Java, _, _, _) => - this.scopeBuilder.registerPolyglotSymbol( - poly.getVisibleName, + override protected def processMethodDefinition( + method: Method.Explicit + ): Unit = { + def where() = + s"`method ${method.typeName.map(_.name + ".").getOrElse("")}${method.methodName.name}`." + val scopeInfo = rootScopeInfo(where, method) + def dataflowInfo() = method.unsafeGetMetadata( + DataflowAnalysis, + "Method definition missing dataflow information." + ) + def frameInfo() = method.unsafeGetMetadata( + FramePointerAnalysis, + "Method definition missing frame information." + ) + + @tailrec + def getContext(tp: Expression): Option[String] = tp match { + case fn: Tpe.Function => getContext(fn.result) + case ctx: Tpe.Context => + ctx.context match { + case lit: Name.Literal => Some(lit.name) + case _ => None + } + case _ => None + } + + val effectContext = method + .getMetadata(TypeSignatures) + .flatMap(sig => getContext(sig.signature)) + + val cons = getTypeDefiningMethod(method) + if (cons != null) { + val fullMethodDefName = + cons.getName ++ Constants.SCOPE_SEPARATOR ++ method.methodName.name + val expressionProcessor = new ExpressionProcessor( + fullMethodDefName, + () => scopeInfo().graph, + () => scopeInfo().graph.rootScope, + dataflowInfo, + fullMethodDefName, + frameInfo + ) + + scopeBuilder.registerMethod( + cons, + method.methodName.name, () => { - val hostSymbol = context.lookupJavaClass(i.getJavaName) - hostSymbol + buildFunction( + method, + effectContext, + cons, + fullMethodDefName, + expressionProcessor + ) } ) - case _: Import.Module => - case _: Error => + } } - private def registerTypeDefinitions(module: Module): Unit = { - val typeDefs = module.bindings.collect { case tp: Definition.Type => tp } - typeDefs.foreach { tpDef => - // Register the atoms and their constructors in scope - val atomDefs = tpDef.members - val asType = scopeBuilder.asModuleScope().getType(tpDef.name.name, true) + override protected def processTypeDefinition(typ: Definition.Type): Unit = { + val atomDefs = typ.members + val asType = + scopeBuilder.asModuleScope().getType(typ.name.name, true) val atomConstructors = atomDefs.map(cons => asType.getConstructors.get(cons.name.name)) atomConstructors .zip(atomDefs) .foreach { case (atomCons, atomDefn) => - registerAtomConstructor(tpDef, atomCons, atomDefn) + registerAtomConstructor(typ, atomCons, atomDefn) } asType.generateGetters(language) } + + override protected def associatedTypeFromResolvedModule( + module: ResolvedModule + ): Type = + asAssociatedType(module.module.unsafeAsModule()) + + override protected def associatedTypeFromResolvedType( + `type`: BindingsMap.ResolvedType, + isStatic: Boolean + ): Type = { + val associatedType = asType(`type`) + if (isStatic) { + associatedType.getEigentype + } else { + associatedType + } + } + + override protected def buildExportScope( + exportedModule: BindingsMap.ExportedModule + ): ImportExportScope = { + val exportedRuntimeMod = exportedModule.module.module.unsafeAsModule() + new ImportExportScope(exportedRuntimeMod) + } + + override protected def buildImportScope( + resolvedImport: BindingsMap.ResolvedImport, + resolvedModule: ResolvedModule + ): ImportExportScope = { + val mod = resolvedModule.module.unsafeAsModule() + resolvedImport.importDef.onlyNames + .map(only => new ImportExportScope(mod, only.map(_.name).asJava)) + .getOrElse(new ImportExportScope(mod)) + } } private def registerAtomConstructor( @@ -270,7 +413,7 @@ class IrToTruffle( val initializationBuilderSupplier : Supplier[AtomConstructor.InitializationBuilder] = () => { - val scopeInfo = rootScopeInfo("atom definition", atomDefn) + val scopeInfo = rootScopeInfo(() => "atom definition", atomDefn) def dataflowInfo() = atomDefn.unsafeGetMetadata( DataflowAnalysis, @@ -376,77 +519,6 @@ class IrToTruffle( } } - private def registerMethodDefinitions(module: Module): Unit = { - val methodDefs = module.bindings.collect { - case method: definition.Method.Explicit => method - } - - methodDefs.foreach(methodDef => { - lazy val where = - s"`method ${methodDef.typeName.map(_.name + ".").getOrElse("")}${methodDef.methodName.name}`." - val scopeInfo = rootScopeInfo(where, methodDef) - def dataflowInfo() = methodDef.unsafeGetMetadata( - DataflowAnalysis, - "Method definition missing dataflow information." - ) - def frameInfo() = methodDef.unsafeGetMetadata( - FramePointerAnalysis, - "Method definition missing frame information." - ) - - @tailrec - def getContext(tp: Expression): Option[String] = tp match { - case fn: Tpe.Function => getContext(fn.result) - case ctx: Tpe.Context => - ctx.context match { - case lit: Name.Literal => Some(lit.name) - case _ => None - } - case _ => None - } - - val effectContext = methodDef - .getMetadata(TypeSignatures) - .flatMap(sig => getContext(sig.signature)) - - val declaredConsOpt = - getTypeAssociatedWithMethodDefinition(methodDef) - - val consOpt = declaredConsOpt.map { c => - if (methodDef.isStatic) { - c.getEigentype - } else { c } - } - - consOpt.foreach { cons => - val fullMethodDefName = - cons.getName ++ Constants.SCOPE_SEPARATOR ++ methodDef.methodName.name - val expressionProcessor = new ExpressionProcessor( - fullMethodDefName, - () => scopeInfo().graph, - () => scopeInfo().graph.rootScope, - dataflowInfo, - fullMethodDefName, - frameInfo - ) - - scopeBuilder.registerMethod( - cons, - methodDef.methodName.name, - () => { - buildFunction( - methodDef, - effectContext, - cons, - fullMethodDefName, - expressionProcessor - ) - } - ) - } - }) - } - private def buildFunction( methodDef: Method.Explicit, effectContext: Option[String], @@ -559,7 +631,7 @@ class IrToTruffle( val scopeName = scopeElements.mkString(Constants.SCOPE_SEPARATOR) - lazy val where = + def where() = s"annotation ${annotation.name} of method ${scopeElements.init .mkString(Constants.SCOPE_SEPARATOR)}" val scopeInfo = rootScopeInfo(where, annotation) @@ -727,133 +799,6 @@ class IrToTruffle( ) } - private def getTypeAssociatedWithMethodDefinition( - methodDef: Method.Explicit - ): Option[Type] = { - methodDef.methodReference.typePointer match { - case None => - Some(scopeAssociatedType) - case Some(tpePointer) => - tpePointer - .getMetadata( - MethodDefinitions.INSTANCE, - classOf[BindingsMap.Resolution] - ) - .map { res => - res.target match { - case binding @ BindingsMap.ResolvedType(_, _) => - asType(binding) - case BindingsMap.ResolvedModule(module) => - asAssociatedType(module.unsafeAsModule()) - case BindingsMap.ResolvedConstructor(_, _) => - throw new CompilerError( - "Impossible, should be caught by MethodDefinitions pass" - ) - case BindingsMap.ResolvedPolyglotSymbol(_, _) => - throw new CompilerError( - "Impossible polyglot symbol, should be caught by MethodDefinitions pass." - ) - case BindingsMap.ResolvedPolyglotField(_, _) => - throw new CompilerError( - "Impossible polyglot field, should be caught by MethodDefinitions pass." - ) - case _: BindingsMap.ResolvedModuleMethod => - throw new CompilerError( - "Impossible module method here, should be caught by MethodDefinitions pass." - ) - case _: BindingsMap.ResolvedExtensionMethod => - throw new CompilerError( - "Impossible static method here, should be caught by MethodDefinitions pass." - ) - case _: BindingsMap.ResolvedConversionMethod => - throw new CompilerError( - "Impossible conversion method here, should be caught by MethodDefinitions pass." - ) - } - } - } - } - - private def registerConversions(module: Module): Unit = { - val conversionDefs = module.bindings.collect { - case conversion: definition.Method.Conversion => - conversion - } - - // Register the conversion definitions in scope - conversionDefs.foreach(methodDef => { - lazy val where = - s"conversion `${methodDef.typeName.map(_.name + ".").getOrElse("")}${methodDef.methodName.name}`." - val scopeInfo = rootScopeInfo(where, methodDef) - - def dataflowInfo() = methodDef.unsafeGetMetadata( - DataflowAnalysis, - "Method definition missing dataflow information." - ) - def frameInfo() = methodDef.unsafeGetMetadata( - FramePointerAnalysis, - "Method definition missing frame information." - ) - - val toOpt = - methodDef.methodReference.typePointer match { - case Some(tpePointer) => - getTypeResolution(tpePointer) - case None => - Some(scopeAssociatedType) - } - val fromOpt = getTypeResolution(methodDef.sourceTypeName) - toOpt.zip(fromOpt).foreach { case (toType, fromType) => - val expressionProcessor = new ExpressionProcessor( - toType.getName ++ Constants.SCOPE_SEPARATOR ++ methodDef.methodName.name, - () => scopeInfo().graph, - () => scopeInfo().graph.rootScope, - dataflowInfo, - methodDef.methodName.name, - frameInfo - ) - - val function = methodDef.body match { - case fn: Function => - val bodyBuilder = - new expressionProcessor.BuildFunctionBody( - methodDef.methodName.name, - fn.arguments, - fn.body, - TypeCheckValueNode.single("conversion", toType), - None, - true - ) - val rootNode = MethodRootNode.build( - language, - expressionProcessor.scope, - scopeBuilder.asModuleScope(), - () => bodyBuilder.bodyNode(), - makeSection(scopeBuilder.getModule, methodDef.location), - toType, - methodDef.methodName.name - ) - val callTarget = rootNode.getCallTarget - val arguments = bodyBuilder.args() - val funcSchema = FunctionSchema - .newBuilder() - .argumentDefinitions(arguments: _*) - .build() - new RuntimeFunction( - callTarget, - null, - funcSchema - ) - case _ => - throw new CompilerError( - "Conversion bodies must be functions at the point of codegen." - ) - } - scopeBuilder.registerConversionMethod(toType, fromType, function) - } - }) - } - // ========================================================================== // === Utility Functions ==================================================== // ========================================================================== @@ -2003,7 +1948,7 @@ class IrToTruffle( setLocation(nameExpr, name.location) } - private class RuntimeNameResolution + final private class RuntimeNameResolution extends NameResolutionAlgorithm[ RuntimeExpression, FramePointer, @@ -2027,7 +1972,8 @@ class IrToTruffle( ReadLocalVariableNode.build(localLink) override protected def resolveGlobalName( - resolvedName: BindingsMap.ResolvedName + resolvedName: BindingsMap.ResolvedName, + relatedIr: IR ): RuntimeExpression = nodeForResolution(resolvedName) @@ -2671,10 +2617,10 @@ class IrToTruffle( } private def scopeAssociatedType = - scopeBuilder.asModuleScope().getAssociatedType + scopeBuilder.getAssociatedType private def rootScopeInfo( - where: => String, + where: () => String, ir: IR ): () => AliasMetadata.RootScope = { def readScopeInfo() = { diff --git a/engine/runtime/src/test/java/org/enso/runtime/test/TypeMetadataPersistanceTest.java b/engine/runtime/src/test/java/org/enso/runtime/test/TypeMetadataPersistanceTest.java index 4665bfdfc5d7..4ec8edf7a57b 100644 --- a/engine/runtime/src/test/java/org/enso/runtime/test/TypeMetadataPersistanceTest.java +++ b/engine/runtime/src/test/java/org/enso/runtime/test/TypeMetadataPersistanceTest.java @@ -4,24 +4,18 @@ import java.io.IOException; import java.util.List; -import org.enso.compiler.data.BindingsMap; -import org.enso.compiler.pass.analyse.types.AtomTypeInterfaceFromBindingsMap; import org.enso.compiler.pass.analyse.types.InferredType; import org.enso.compiler.pass.analyse.types.TypeRepresentation; import org.enso.persist.Persistance; import org.enso.pkg.QualifiedName; +import org.enso.scala.wrapper.ScalaConversions; import org.junit.Test; -import scala.jdk.javaapi.CollectionConverters$; /** * Currently the static type inference pass is optional and it is not computed as part of the cache * indexing. */ public class TypeMetadataPersistanceTest { - private static scala.collection.immutable.List makeScalaList(List list) { - return CollectionConverters$.MODULE$.asScala(list).toList(); - } - private static T serde(Class clazz, T l) throws IOException { var arr = Persistance.write(l, null); var ref = Persistance.read(arr, null); @@ -41,17 +35,7 @@ public void writeSomeInferredType() throws Exception { } private TypeRepresentation.TypeObject mockObject() { - var fqn = new QualifiedName(makeScalaList(List.of("mod")), "Test"); - return new TypeRepresentation.TypeObject(fqn, mockAtomType()); - } - - private AtomTypeInterfaceFromBindingsMap mockAtomType() { - scala.collection.immutable.List params = makeScalaList(List.of()); - var ctorArgs = - makeScalaList( - List.of(new BindingsMap.Argument("arg", false, Persistance.Reference.none()))); - var constructors = makeScalaList(List.of(new BindingsMap.Cons("ctor", ctorArgs, false))); - return new AtomTypeInterfaceFromBindingsMap( - new BindingsMap.Type("Test", params, constructors, false)); + var fqn = new QualifiedName(ScalaConversions.seq(List.of("mod")).toList(), "Test"); + return new TypeRepresentation.TypeObject(fqn); } } diff --git a/lib/java/persistance/src/main/java/org/enso/persist/PerMap.java b/lib/java/persistance/src/main/java/org/enso/persist/PerMap.java index 0bc1445b72de..084a7748e031 100644 --- a/lib/java/persistance/src/main/java/org/enso/persist/PerMap.java +++ b/lib/java/persistance/src/main/java/org/enso/persist/PerMap.java @@ -5,6 +5,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import org.openide.util.lookup.Lookups; final class PerMap { @@ -34,7 +35,7 @@ private PerMap() { throw new IllegalStateException( "Multiple registrations for ID " + p.id + " " + prevId + " != " + p); } - hash += p.id; + hash = Objects.hash(hash, p.id); var prevType = types.put(p.clazz, p); if (prevType != null) { throw new IllegalStateException( diff --git a/lib/java/test-utils/src/main/java/org/enso/test/utils/ModuleUtils.java b/lib/java/test-utils/src/main/java/org/enso/test/utils/ModuleUtils.java index e715f111a08f..956f5b834354 100644 --- a/lib/java/test-utils/src/main/java/org/enso/test/utils/ModuleUtils.java +++ b/lib/java/test-utils/src/main/java/org/enso/test/utils/ModuleUtils.java @@ -4,6 +4,11 @@ import java.util.List; import java.util.Map; import org.enso.compiler.context.CompilerContext.Module; +import org.enso.compiler.core.IR; +import org.enso.compiler.core.ir.Diagnostic; +import org.enso.compiler.core.ir.DiagnosticStorage; +import org.enso.compiler.core.ir.Expression; +import org.enso.compiler.core.ir.module.scope.definition.Method; import org.enso.compiler.data.BindingsMap.DefinedEntity; import org.enso.compiler.data.BindingsMap.ResolvedImport; import org.enso.compiler.data.BindingsMap.ResolvedName; @@ -68,4 +73,70 @@ private static Map> getExportedSymbols(Module module) }); return bindings; } + + public static List getImmediateDiagnostics(IR ir) { + return CollectionConverters.asJava(ir.getDiagnostics().toList()); + } + + public static List getDescendantsDiagnostics(IR ir) { + return CollectionConverters.asJava( + ir.preorder() + .flatMap( + (node) -> { + DiagnosticStorage diagnostics = node.getDiagnostics(); + if (diagnostics != null) { + return diagnostics.toList(); + } else { + return scala.collection.immutable.List$.MODULE$.empty(); + } + })); + } + + public static Method findStaticMethod(org.enso.compiler.core.ir.Module module, String name) { + var option = + module + .bindings() + .find( + (def) -> + (def instanceof Method binding) + && binding.methodReference().typePointer().isEmpty() + && binding.methodReference().methodName().name().equals(name)); + + if (option.isEmpty()) { + throw new IllegalStateException("The method " + name + " should exist within the IR."); + } + return (Method) option.get(); + } + + public static Method findMemberMethod( + org.enso.compiler.core.ir.Module module, String typeName, String name) { + var option = + module + .bindings() + .find( + (def) -> + (def instanceof Method binding) + && binding.methodReference().typePointer().isDefined() + && binding.methodReference().typePointer().get().name().equals(typeName) + && binding.methodReference().methodName().name().equals(name)); + + if (option.isEmpty()) { + throw new IllegalStateException("The method " + name + " should exist within the IR."); + } + return (Method) option.get(); + } + + public static Expression.Binding findAssignment(IR ir, String name) { + var option = + ir.preorder() + .find( + (node) -> + (node instanceof Expression.Binding binding) + && binding.name().name().equals(name)); + if (option.isEmpty()) { + throw new IllegalStateException( + "The binding `" + name + " = ...` should exist within the IR."); + } + return (Expression.Binding) option.get(); + } } diff --git a/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala b/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala index 3adb2ecfd257..49fde351125c 100644 --- a/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala +++ b/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala @@ -96,9 +96,6 @@ object Contact { * edition * @param componentGroups the description of component groups provided by this * package - * @param originalJson a Json object holding the original values that this - * Config was created from, used to preserve configuration - * keys that are not known */ case class Config( name: String, diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso index 832212d22f84..e74970f4350d 100644 --- a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso +++ b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso @@ -15,3 +15,6 @@ type Any @Builtin_Type type Default_Comparator + + ## PRIVATE + less_than_builtin left right = @Builtin_Method "Default_Comparator.less_than_builtin" diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso index b5b066c3832b..916ca2f6d055 100644 --- a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso +++ b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso @@ -32,4 +32,6 @@ type Project_Description name self = self.prim_config.name + enso_project_builtin module = @Builtin_Method "Project_Description.enso_project_builtin" + enso_project = Project_Description.enso_project_builtin Nothing