Skip to content

Commit

Permalink
Infer method calls on known types and warn about nonexistent methods (#…
Browse files Browse the repository at this point in the history
…11399)

- Implements #9812
- Allows us to propagate type inference through method calls, at least in a basic way.
- Adds a 'lint warning': when calling a method on an object of a known specific (non-`Any`) type that does not exist on that type, a warning is reported, because such a call would always result in `No_Such_Method` error at runtime.
- `Any` has special behaviour - if `x : Any` we don't know what `x` might be, so we allow all methods on it and defer to the runtime to see if they will be valid or not.
- This check is not proving correctness, but instead it's only triggering for 'provably wrong' situations.
- Includes changes from #11955:
- removing obsolete `AtomTypeInterface`,
- simplifying `TypeRepresentation` to be a plain data structure,
- and removing a cycle from IR in `BindingsMap` in favour of relying on `StaticModuleScope`.
  • Loading branch information
radeusgd authored Jan 8, 2025
1 parent 10bb0ae commit 787372e
Show file tree
Hide file tree
Showing 59 changed files with 3,442 additions and 1,011 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions engine/runtime-compiler/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,21 @@ public static <T> T getMetadataOrNull(IR ir, IRProcessingPass pass, Class<T> exp
public static <T> T getMetadata(IR ir, IRProcessingPass pass, Class<T> 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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>This is done in two places:
*
* <ol>
* <li>in the compiler, gathering just the types to build StaticModuleScope,
* <li>in the runtime, building Truffle nodes for the interpreter.
* </ol>
*
* <p>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<TypeScopeReferenceType, ImportExportScopeType> {
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.
*
* <p>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.
*
* <p>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.
*
* <ul>
* <li>For a member method, this will be its parent type.
* <li>For a static method, this will be the eigentype of the type on which it is defined.
* <li>For a module method, this will be the type associated with the module.
* </ul>
*/
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.
*
* <p>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.
*
* <p>Such scope is then registered with the scope builder using {@code addImport}.
*/
protected abstract ImportExportScopeType buildImportScope(
BindingsMap.ResolvedImport resolvedImport, BindingsMap.ResolvedModule resolvedModule);
}
Loading

0 comments on commit 787372e

Please sign in to comment.