diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Boolean.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Boolean.enso index d7e0479632..53918f7619 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Boolean.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Boolean.enso @@ -1,7 +1,8 @@ import project.Any.Any import project.Nothing.Nothing -from project.Data.Boolean.Boolean export False, True +export project.Data.Boolean.Boolean.False +export project.Data.Boolean.Boolean.True ## A type with only two possible values. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso index bc616a65c4..440fb021fa 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso @@ -93,14 +93,88 @@ export project.System.Platform export project.System.Process export project.System.Process.Exit_Code.Exit_Code export project.Warning.Warning -from project.Data.Boolean export Boolean, False, True -from project.Data.Json.Extensions export all -from project.Data.Numbers export Float, Integer, Number -from project.Data.Range.Extensions export all -from project.Data.Statistics.Extensions export all -from project.Data.Text.Extensions export all -from project.Data.Text.Regex export regex -from project.Function export all -from project.Meta.Enso_Project export enso_project -from project.Network.Extensions export all -from project.System.File_Format export Auto_Detect, Bytes, File_Format, Infer, JSON_Format, Plain_Text_Format +export project.Data.Boolean.Boolean +export project.Data.Boolean.Boolean.False +export project.Data.Boolean.Boolean.True +export project.Data.Json.Extensions.to_json +export project.Data.Json.Extensions.to_js_object +export project.Data.Numbers.Float +export project.Data.Numbers.Integer +export project.Data.Numbers.Number +export project.Data.Range.Extensions.up_to +export project.Data.Range.Extensions.down_to +export project.Data.Statistics.Extensions.compute +export project.Data.Statistics.Extensions.compute_bulk +export project.Data.Statistics.Extensions.running +export project.Data.Statistics.Extensions.running_bulk +export project.Data.Statistics.Extensions.rank_data +export project.Data.Text.Extensions.reverse +export project.Data.Text.Extensions.each +export project.Data.Text.Extensions.at +export project.Data.Text.Extensions.get +export project.Data.Text.Extensions.first +export project.Data.Text.Extensions.second +export project.Data.Text.Extensions.last +export project.Data.Text.Extensions.characters +export project.Data.Text.Extensions.find +export project.Data.Text.Extensions.find_all +export project.Data.Text.Extensions.match +export project.Data.Text.Extensions.to_regex +export project.Data.Text.Extensions.split +export project.Data.Text.Extensions.tokenize +export project.Data.Text.Extensions.replace +export project.Data.Text.Extensions.cleanse +export project.Data.Text.Extensions.words +export project.Data.Text.Extensions.lines +export project.Data.Text.Extensions.insert +export project.Data.Text.Extensions.from +export project.Data.Text.Extensions.is_digit +export project.Data.Text.Extensions.is_whitespace +export project.Data.Text.Extensions.bytes +export project.Data.Text.Extensions.from_bytes +export project.Data.Text.Extensions.utf_8 +export project.Data.Text.Extensions.from_utf_8 +export project.Data.Text.Extensions.char_vector +export project.Data.Text.Extensions.from_char_vector +export project.Data.Text.Extensions.codepoints +export project.Data.Text.Extensions.from_codepoints +export project.Data.Text.Extensions.starts_with +export project.Data.Text.Extensions.ends_with +export project.Data.Text.Extensions.contains +export project.Data.Text.Extensions.repeat +export project.Data.Text.Extensions.take +export project.Data.Text.Extensions.drop +export project.Data.Text.Extensions.to_case +export project.Data.Text.Extensions.pad +export project.Data.Text.Extensions.trim +export project.Data.Text.Extensions.locate +export project.Data.Text.Extensions.locate_all +export project.Data.Text.Extensions.index_of +export project.Data.Text.Extensions.last_index_of +export project.Data.Text.Extensions.parse_float +export project.Data.Text.Extensions.parse_integer +export project.Data.Text.Extensions.parse_json +export project.Data.Text.Extensions.parse_date +export project.Data.Text.Extensions.parse_date_time +export project.Data.Text.Extensions.parse_time_of_day +export project.Data.Text.Extensions.parse_time_zone +export project.Data.Text.Extensions.substring +export project.Data.Text.Extensions.slice_text +export project.Data.Text.Extensions.split_find_delimiters +export project.Data.Text.Regex.regex +export project.Function.Function +export project.Function.identity +export project.Function.flip +export project.Function.const +export project.Function.curry +export project.Function.uncurry +export project.Meta.Enso_Project.enso_project +export project.Network.Extensions.to_uri +export project.Network.Extensions.fetch +export project.Network.Extensions.post +export project.System.File_Format.Auto_Detect +export project.System.File_Format.Bytes +export project.System.File_Format.File_Format +export project.System.File_Format.Infer +export project.System.File_Format.JSON_Format +export project.System.File_Format.Plain_Text_Format diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Main.enso index b4e987ae8c..12fbb00595 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Main.enso @@ -1,4 +1,5 @@ export project.Column_Description.Column_Description +import project.Connection export project.Connection.Client_Certificate.Client_Certificate export project.Connection.Connection_Options.Connection_Options export project.Connection.Credentials.Credentials @@ -9,5 +10,9 @@ export project.Connection.SQLite_Format.SQLite_Format export project.Connection.SSL_Mode.SSL_Mode export project.SQL_Query.SQL_Query export project.Update_Action.Update_Action -from project.Extensions.Upload_Database_Table export all -from project.Extensions.Upload_In_Memory_Table export all +export project.Extensions.Upload_Database_Table.update_rows +export project.Extensions.Upload_Database_Table.select_into_database_table +export project.Extensions.Upload_Database_Table.delete_rows +export project.Extensions.Upload_In_Memory_Table.update_rows +export project.Extensions.Upload_In_Memory_Table.select_into_database_table +export project.Extensions.Upload_In_Memory_Table.delete_rows diff --git a/distribution/lib/Standard/Geo/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Geo/0.0.0-dev/src/Main.enso index b5d1f18397..6b1b874295 100644 --- a/distribution/lib/Standard/Geo/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Geo/0.0.0-dev/src/Main.enso @@ -2,7 +2,7 @@ from Standard.Base import all from Standard.Table import Table -from project.Geo_Json export geo_json_to_table +export project.Geo_Json.geo_json_to_table ## UNSTABLE ICON location diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Main.enso index 81c2939198..cd4ce55bb7 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Main.enso @@ -25,7 +25,14 @@ export project.Table.Table export project.Value_Type.Auto export project.Value_Type.Bits export project.Value_Type.Value_Type -from project.Constants export all -from project.Expression export expr -from project.Extensions.Column_Vector_Extensions export all -from project.Extensions.Table_Conversions export all +export project.Constants.Previous_Value +export project.Constants.Report_Unmatched +export project.Expression.expr +export project.Extensions.Column_Vector_Extensions.to_column +export project.Extensions.Column_Vector_Extensions.compute +export project.Extensions.Column_Vector_Extensions.compute_bulk +export project.Extensions.Column_Vector_Extensions.running +export project.Extensions.Table_Conversions.to_table +export project.Extensions.Table_Conversions.from_objects +export project.Extensions.Table_Conversions.parse_to_table +export project.Extensions.Table_Conversions.write_table diff --git a/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso index e417b167f0..661c6d22bf 100644 --- a/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso @@ -12,5 +12,19 @@ export project.Faker.Faker export project.Problems export project.Suite.Suite export project.Test.Test -from project.Extensions export all +export project.Extensions.should_fail_with +export project.Extensions.should_equal +export project.Extensions.should_equal_type +export project.Extensions.should_not_equal +export project.Extensions.should_not_equal_type +export project.Extensions.should_start_with +export project.Extensions.should_end_with +export project.Extensions.should_succeed +export project.Extensions.should_be_a +export project.Extensions.should_be_true +export project.Extensions.should_be_false +export project.Extensions.should_contain_the_same_elements_as +export project.Extensions.should_only_contain_elements_in +export project.Extensions.should_contain +export project.Extensions.should_not_contain diff --git a/distribution/lib/Standard/Visualization/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Visualization/0.0.0-dev/src/Main.enso index e339843178..14c2e0e2fb 100644 --- a/distribution/lib/Standard/Visualization/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Visualization/0.0.0-dev/src/Main.enso @@ -5,5 +5,5 @@ export project.AI export project.Helpers export project.Id.Id export project.Preprocessor -from project.File_Upload export file_uploading +export project.File_Upload.file_uploading diff --git a/docs/infrastructure/benchmarks.md b/docs/infrastructure/benchmarks.md index 5d6decac86..4b63843e95 100644 --- a/docs/infrastructure/benchmarks.md +++ b/docs/infrastructure/benchmarks.md @@ -55,6 +55,16 @@ to 0, and to run `withDebug` command like this: withDebug --debugger benchOnly -- ``` +Another option that does not require changing the source code is to run +something like + +``` +sbt:runtime-benchmarks> run -w 1 -i 1 -f 1 -jvmArgs -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=localhost:8000 org.enso.compiler.benchmarks.module.ImportStandardLibrariesBenchmark.importStandardLibraries +``` + +This command will run the `importStandardLibraries` benchmark in fork waiting +for the debugger to attach. + ## Standard library benchmarks Unlike the Engine micro benchmarks, these benchmarks are written entirely in diff --git a/docs/syntax/functions.md b/docs/syntax/functions.md index 27fabfca70..3a05d7f630 100644 --- a/docs/syntax/functions.md +++ b/docs/syntax/functions.md @@ -132,18 +132,26 @@ Number.floor self = case self of ... ``` -3. **As a Function with an Explicit `self` Argument:** A function defined with - the type of the `self` argument specified to be a type. +3. **As a module method:** A function defined outside the body of a type and + without explicit `self` argument, is considered a _module method_. ```ruby -floor (self : Number) = case self of - Integer -> ... +module_method x y = x + y ``` -If the user does not explicitly specify the `this` argument by name when +If the user does not explicitly specify the `self` argument by name when defining a method (e.g. they use the `Type.name` syntax), it is implicitly added to the start of the argument list. +Note that the following methods defined in the body of type and as an extension +method are equivalent: + +``` +type My_Type + method self = 42 +My_Type.method self = 42 +``` + ## Calling Functions and Methods Enso makes the distinction between functions and methods. Methods are entities diff --git a/docs/syntax/imports.md b/docs/syntax/imports.md index 3c93162a42..f768dde9bb 100644 --- a/docs/syntax/imports.md +++ b/docs/syntax/imports.md @@ -18,18 +18,22 @@ code from modules. - [Import Syntax](#import-syntax) - [Qualified Imports](#qualified-imports) - [Unqualified Imports](#unqualified-imports) +- [Imports with multiple targets](#imports-with-multiple-targets) - [Export Syntax](#export-syntax) - [Qualified Exports](#qualified-exports) - [Unqualified Exports](#unqualified-exports) - [Visibility of Export Bindings](#visibility-of-export-bindings) + - [Implicit exports](#implicit-exports) + - [Defined entities in a module](#defined-entities-in-a-module) + - [Synthetic module](#synthetic-module) +- [Exports with multiple targets](#exports-with-multiple-targets) ## Qualified Names In the following text, **entity** shall denote a module, a method (instance, -static, foreign), type, or a type constructor. In other words, an _entity_ is -anything that can be assigned to a variable. +static, extension, conversion or foreign), type, or a type constructor. Both imports and exports require the use of qualified entity names. A qualified name consists of the library namespace (usually organization under which its @@ -95,6 +99,30 @@ Imports in Enso _may_ introduce ambiguous symbols, which is treated as a compilation error. Ideally, the error should be delayed until one of the ambiguous symbols is _used_ in Enso code. +## Imports with multiple targets + +Import of one symbol can resolve to multiple targets in case of extension or +conversion methods. For example, the following import in `Main.enso`: +`A_Module.enso`: + +``` +type My_Type +type Other_Type +My_Type.method = 42 +Other_Type.method = 42 +``` + +`Main.enso`: + +``` +import project.A_Module.method +``` + +imports both `My_Type.method` and `Other_Type.method` methods. + +Note that `import project.A_Module.My_Type.method` would lead to a compilation +error, as it is only possible to import constructors from a type, not methods. + ## Export Syntax In order to allow for easy composition and aggregation of code, Enso provides @@ -112,31 +140,19 @@ the name provided after the `as` keyword, if provided). ### Unqualified Exports -Unqualified exports are broken up into three main categories: +Unlike imports, exports cannot be used with the `all` and `hiding` keywords. So +the only supported syntax is to export a list of names with _restricted +exports_. -1. **Unrestricted Exports:** These export all symbols from the module or type - into the current scope. They consist of the keyword `from`, followed by a - qualified module name, followed by an optional rename part (using the `as` - keyword), then the keywords `export all`. For example: - ``` - from Standard.Base.Data.List as Builtin_List export all - ``` -2. **Restricted Exports:** These export a specified set of names, behaving as - though they were redefined in the current scope. They consist of the keyword - `from`, followed by a qualified module or type name (with optional - `as`-rename), then the word `export` followed by a coma-separated list of - names to be exported. For example: - ``` - from Standard.Base.Data.List export Cons, Nil, from_vector - ``` -3. **Hiding Exports:** These are the inverse of restricted exports, and export - _all_ symbols other than the named ones. They consist of the `from` keyword, - followed by a qualified module or type name (with optional `as`-rename), then - the words `export all hiding`, followed by a coma-separated list of names to - be excluded from the export. For example: - ``` - from Standard.Base.Data.List export all hiding from_vector, Nil - ``` +**Restricted Exports:** These export a specified set of names, behaving as +though they were redefined in the current scope. They consist of the keyword +`from`, followed by a qualified module or type name (with optional `as`-rename), +then the word `export` followed by a coma-separated list of names to be +exported. For example: + +``` +from Standard.Base.Data.List export Cons, Nil, from_vector +``` In essence, an export allows the user to "paste" the contents of the module or type being exported into the module declaring the export. This means that @@ -146,3 +162,63 @@ exports that create name clashes must be resolved at the _export_ site. Bindings exported from a module `X` are available in an identical fashion to bindings that are _defined_ in the module `X`. + +### Implicit exports + +The compiler inserts implicit exports for entities defined in a module and for +submodules of a _synthetic module_. A synthetic module is basically a directory +in the source structure. + +#### Defined entities in a module + +Entities defined in a module are automatically exported from the module. This +means that the following modules are semantically identical: + +``` +type My_Type +method x = x +``` + +``` +export project.Module.My_Type +export project.Module.method +type My_Type +method x = x +``` + +#### Synthetic module + +Consider a project named `Proj` with the following source structure: + +`Proj/src/Synthetic_Mod/Module.enso`: + +``` +type My_Type +``` + +`Proj/src/Main.enso`: + +``` +import project.Synthetic_Mod.Module.My_Type +``` + +We can import submodules of `Synthetic_Mod`, because the compiler automatically +inserts exports for them. Internally, `Synthetic_Mod` is represented as a module +with single export: + +``` +export project.Synthetic_Mod.Module +``` + +## Exports with multiple targets + +Export of a single symbol can be resolved to multiple targets (entities) in case +of extension or conversion methods. Similarly to +[imports with multiple targets](#imports-with-multiple-targets), the following +export in `A_Module.enso`: + +``` +export project.A_Module.export +``` + +exports both `My_Type.method` and `Other_Type.method` methods. diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java index 9b838d29e1..685e27299f 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java @@ -484,7 +484,6 @@ public class IRDumper { GraphVizNode.Builder.fromIr(exportIr) .addLabelLine("isSynthetic: " + exportModIr.isSynthetic()) .addLabelLine("name: " + exportModIr.name().name()) - .addLabelLine("isAll: " + exportModIr.isAll()) .build(); addNode(node); } @@ -577,28 +576,13 @@ public class IRDumper { bldr.addLabelLine("resolvedImports: "); for (int i = 0; i < bindingsMap.resolvedImports().size(); i++) { var resolvedImport = bindingsMap.resolvedImports().apply(i); - switch (resolvedImport.target()) { + var firstImpTarget = resolvedImport.targets().head(); + switch (firstImpTarget) { case ResolvedType resolvedType -> bldr.addLabelLine( " - ResolvedType(" + resolvedType.tp().name() + ")"); case BindingsMap.ResolvedModule resolvedModule -> bldr.addLabelLine( " - ResolvedModule(" + resolvedModule.qualifiedName() + ")"); - default -> throw unimpl(resolvedImport.target()); - } - } - } - - if (bindingsMap.resolvedExports().isEmpty()) { - bldr.addLabelLine("resolvedExports: []"); - } else { - bldr.addLabelLine("resolvedExports: "); - for (int i = 0; i < bindingsMap.resolvedExports().size(); i++) { - var resolvedExport = bindingsMap.resolvedExports().apply(i); - switch (resolvedExport.target()) { - case ResolvedType resolvedType -> bldr.addLabelLine( - " - ResolvedType(" + resolvedType.tp().name() + ")"); - case BindingsMap.ResolvedModule resolvedModule -> bldr.addLabelLine( - " - ResolvedModule(" + resolvedModule.qualifiedName() + ")"); - default -> throw unimpl(resolvedExport.target()); + default -> throw unimpl(firstImpTarget); } } } diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/ExportSymbolAnalysis.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/ExportSymbolAnalysis.java deleted file mode 100644 index 4a3247f070..0000000000 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/ExportSymbolAnalysis.java +++ /dev/null @@ -1,155 +0,0 @@ -package org.enso.compiler.pass.analyse; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -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.expression.errors.ImportExport; -import org.enso.compiler.core.ir.module.scope.Export; -import org.enso.compiler.data.BindingsMap; -import org.enso.compiler.pass.IRPass; -import scala.collection.immutable.Seq; -import scala.jdk.javaapi.CollectionConverters; - -/** - * This pass ensures that all the symbols that are exported exist. If not, an IR error is generated. - */ -public final class ExportSymbolAnalysis implements IRPass { - public static final ExportSymbolAnalysis INSTANCE = new ExportSymbolAnalysis(); - private static scala.collection.immutable.List precursorPasses; - private UUID uuid; - - private ExportSymbolAnalysis() {} - - @Override - public UUID key() { - return null; - } - - @Override - public void org$enso$compiler$pass$IRPass$_setter_$key_$eq(UUID v) { - this.uuid = v; - } - - @Override - public Seq precursorPasses() { - if (precursorPasses == null) { - List passes = List.of(BindingAnalysis$.MODULE$, ImportSymbolAnalysis$.MODULE$); - precursorPasses = CollectionConverters.asScala(passes).toList(); - } - return precursorPasses; - } - - @Override - @SuppressWarnings("unchecked") - public Seq invalidatedPasses() { - Object obj = scala.collection.immutable.Nil$.MODULE$; - return (scala.collection.immutable.List) obj; - } - - @Override - public Module runModule(Module moduleIr, ModuleContext moduleContext) { - List exportErrors = new ArrayList<>(); - var bindingsMap = (BindingsMap) moduleIr.passData().get(BindingAnalysis$.MODULE$).get(); - - moduleIr - .exports() - .foreach( - export -> - switch (export) { - case Export.Module exportMod -> { - var exportNameParts = exportMod.name().parts(); - var symbolName = exportMod.name().parts().last(); - assert exportNameParts.size() > 1; - var moduleOrTypeName = exportNameParts.apply(exportNameParts.size() - 2); - var foundResolvedExp = findResolvedExportForIr(export, bindingsMap); - if (foundResolvedExp == null) { - exportErrors.add( - ImportExport.apply( - symbolName, - new ImportExport.SymbolDoesNotExist( - symbolName.name(), moduleOrTypeName.name()), - ImportExport.apply$default$3(), - ImportExport.apply$default$4())); - } else { - if (exportMod.onlyNames().isDefined()) { - assert exportMod.onlyNames().isDefined(); - var exportedSymbols = exportMod.onlyNames().get(); - exportedSymbols.foreach( - exportedSymbol -> { - var foundSymbols = - foundResolvedExp - .target() - .findExportedSymbolsFor(exportedSymbol.name()); - if (foundSymbols.isEmpty()) { - exportErrors.add( - ImportExport.apply( - exportedSymbol, - new ImportExport.SymbolDoesNotExist( - exportedSymbol.name(), moduleOrTypeName.name()), - ImportExport.apply$default$3(), - ImportExport.apply$default$4())); - } - return null; - }); - } - } - yield null; - } - default -> export; - }); - - if (exportErrors.isEmpty()) { - return moduleIr; - } else { - return moduleIr.copy( - moduleIr.imports(), - CollectionConverters.asScala(exportErrors).toList(), - moduleIr.bindings(), - moduleIr.location(), - moduleIr.passData(), - moduleIr.diagnostics(), - moduleIr.id()); - } - } - - @Override - public Expression runExpression(Expression ir, InlineContext inlineContext) { - return ir; - } - - /** - * Finds a resolved export that corresponds to the export IR. - * - * @param exportIr Export IR that is being resolved - * @param bindingsMap Bindings map of the module that contains the export IR - * @return null if no resolved export was found, otherwise the resolved export - */ - private BindingsMap.ExportedModule findResolvedExportForIr( - Export exportIr, BindingsMap bindingsMap) { - switch (exportIr) { - case Export.Module exportedModIr -> { - var exportedModName = exportedModIr.name().name(); - var foundResolvedExp = - bindingsMap - .resolvedExports() - .find( - resolvedExport -> { - var resolvedExportName = resolvedExport.target().qualifiedName(); - return resolvedExportName.toString().equals(exportedModName); - }); - return foundResolvedExp.isEmpty() ? null : foundResolvedExp.get(); - } - default -> throw new IllegalStateException("Unexpected value: " + exportIr); - } - } - - @Override - public T updateMetadataInDuplicate(T sourceIr, T copyOfIr) { - return IRPass.super.updateMetadataInDuplicate(sourceIr, copyOfIr); - } -} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateModuleAnalysis.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateModuleAnalysis.java index aabd12b82f..644b05cde5 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateModuleAnalysis.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateModuleAnalysis.java @@ -72,19 +72,24 @@ public final class PrivateModuleAnalysis implements IRPass { .resolvedImports() .foreach( resolvedImp -> { - var importedModule = resolvedImp.target().module().unsafeAsModule("should succeed"); - var importedModuleName = importedModule.getName().toString(); - var importedModulePackage = importedModule.getPackage(); - if (currentPackage != null - && !currentPackage.equals(importedModulePackage) - && importedModule.isPrivate()) { - importErrors.add( - ImportExport.apply( - resolvedImp.importDef(), - new ImportExport.ImportPrivateModule(importedModuleName), - ImportExport.apply$default$3(), - ImportExport.apply$default$4())); - } + var importedTargets = resolvedImp.targets(); + importedTargets.foreach( + importedTarget -> { + var importedModule = importedTarget.module().unsafeAsModule("should succeed"); + var importedModuleName = importedModule.getName().toString(); + var importedModulePackage = importedModule.getPackage(); + if (currentPackage != null + && !currentPackage.equals(importedModulePackage) + && importedModule.isPrivate()) { + importErrors.add( + ImportExport.apply( + resolvedImp.importDef(), + new ImportExport.ImportPrivateModule(importedModuleName), + ImportExport.apply$default$3(), + ImportExport.apply$default$4())); + } + return null; + }); return null; }); @@ -103,7 +108,7 @@ public final class PrivateModuleAnalysis implements IRPass { .getDirectlyExportedModules() .foreach( expModule -> { - var expModuleRef = expModule.target().module().unsafeAsModule("should succeed"); + var expModuleRef = expModule.module().module().unsafeAsModule("should succeed"); if (expModuleRef.isPrivate() && !isCurrentModuleSynthetic) { var associatedExportIR = findExportIRByName(moduleIr, expModuleRef.getName()); assert associatedExportIR.isDefined(); diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/phase/ImportResolverAlgorithm.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/phase/ImportResolverAlgorithm.java index 2e4c3c4a43..5644a0bcc5 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/phase/ImportResolverAlgorithm.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/phase/ImportResolverAlgorithm.java @@ -2,13 +2,21 @@ package org.enso.compiler.phase; import java.io.IOException; import java.util.List; -import java.util.Objects; +import java.util.stream.Collectors; import org.enso.compiler.core.CompilerError; import org.enso.editions.LibraryName; -import scala.Tuple2; public abstract class ImportResolverAlgorithm< - Result, Module, Import, Export, ResolvedType, ResolvedModule> { + Result, + Module, + Import, + Export, + ResolvedType, + ResolvedModule, + ResolvedConstructor, + ResolvedModuleMethod, + ResolvedExtensionMethod, + ResolvedConversionMethod> { protected ImportResolverAlgorithm() {} protected abstract String nameForImport(Import imp); @@ -19,6 +27,14 @@ public abstract class ImportResolverAlgorithm< protected abstract String nameForType(ResolvedType e); + protected abstract String nameForConstructor(ResolvedConstructor cons); + + protected abstract String nameForModuleMethod(ResolvedModuleMethod method); + + protected abstract String nameForExtensionMethod(ResolvedExtensionMethod method); + + protected abstract String nameForConversionMethod(ResolvedConversionMethod method); + /** * Returns a list of all the exports from the module of the given symbol * @@ -27,20 +43,21 @@ public abstract class ImportResolverAlgorithm< */ protected abstract java.util.List exportsFor(Module module, String symbol); - protected abstract boolean isAll(Export ex); - /** * @return {@code null} or list of named imports */ protected abstract java.util.List onlyNames(Export ex); - /** - * @return {@code null} or list of named imports - */ - protected abstract java.util.List hiddenNames(Export ex); - protected abstract java.util.List definedEntities(Import name); + protected abstract java.util.List definedConstructors(Import name); + + protected abstract java.util.List definedModuleMethods(Import imp); + + protected abstract java.util.List definedExtensionMethods(Import imp); + + protected abstract java.util.List definedConversionMethods(Import imp); + /** * Ensure library is loaded and load a module. * @@ -56,13 +73,36 @@ public abstract class ImportResolverAlgorithm< protected abstract Result createResolvedType( Import imp, java.util.List exp, ResolvedType m); + protected abstract Result createResolvedConstructor( + Import imp, java.util.List exp, ResolvedConstructor cons); + + protected abstract Result createResolvedModuleMethod( + Import imp, java.util.List exp, ResolvedModuleMethod moduleMethod); + + protected abstract Result createResolvedExtensionMethods( + Import imp, + java.util.List exp, + java.util.List extensionMethods); + + protected abstract Result createResolvedConversionMethods( + Import imp, + java.util.List exp, + java.util.List conversionMethods); + protected abstract Result createErrorPackageCoundNotBeLoaded( Import imp, String impName, String loadingError); protected abstract Result createErrorModuleDoesNotExist(Import imp, String impName); /** - * Resolves given {@code Import} in context of given {@code Module}. + * Resolves given {@code Import} in context of given {@code Module}. Handles only an import of a + * specific symbol with syntax {@code import project.Module.Symbol}. The {@code from + * project.Module import Symbol} is handled by this algorithm by importing only the {@code Module} + * and not resolving the {@code Symbol}. + * + *

With the first syntax of import ({@code import project.Module.Symbol}), one case only import + * module, type from a module, constructor from a type or a module method. Static, extension and + * conversion methods can be imported only with the {@code from ... import all} syntax. * * @param module the module that wants to import something * @param imp the import to resolve @@ -76,61 +116,6 @@ public abstract class ImportResolverAlgorithm< private Result tryResolveImportNew(Module module, Import imp) { var impName = nameForImport(imp); var exp = exportsFor(module, impName); - var fromAllExports = exp.stream().filter(ex -> isAll(ex)).toList(); - if (fromAllExports.size() >= 2) { - // Detect potential conflicts when importing all and hiding names for the exports of the same - // module - var unqualifiedImports = fromAllExports.stream().filter(e -> onlyNames(e) == null).toList(); - var qualifiedImports = - fromAllExports.stream() - .map( - e -> { - var onlyNames = onlyNames(e); - if (onlyNames != null) { - return onlyNames.stream().toList(); - } else { - return null; - } - }) - .filter(Objects::nonNull) - .toList(); - var importsWithHiddenNames = - fromAllExports.stream() - .map( - e -> { - var hiddenNames = hiddenNames(e); - if (hiddenNames != null) { - return new Tuple2<>(e, hiddenNames); - } else { - return null; - } - }) - .filter(Objects::nonNull) - .toList(); - - for (var h : importsWithHiddenNames) { - var e = h._1; - var hidden = h._2; - var unqualifiedConflicts = - unqualifiedImports.stream().filter(x -> !x.equals(e)).filter(Objects::nonNull).toList(); - if (!unqualifiedConflicts.isEmpty()) { - throw HiddenNamesConflict.shadowUnqualifiedExport(nameForExport(e), hidden); - } - } - for (var h : importsWithHiddenNames) { - var e = h._1; - var hidden = h._2; - var qualifiedConflicts = - qualifiedImports.stream() - .filter(Objects::nonNull) - .flatMap(x -> x.stream()) - .filter(f -> hidden.stream().filter(x -> f.equals(x)).findAny().isPresent()) - .toList(); - if (!qualifiedConflicts.isEmpty()) { - throw HiddenNamesConflict.shadowQualifiedExport(nameForExport(e), qualifiedConflicts); - } - } - } var parts = partsForImport(imp); if (parts.size() < 2) { throw new CompilerError( @@ -141,14 +126,28 @@ public abstract class ImportResolverAlgorithm< var m = loadLibraryModule(libraryName, impName); if (m != null) { return createResolvedImport(imp, exp, m); - } else { - var typ = tryResolveAsTypeNew(imp); - if (typ != null) { - return createResolvedType(imp, exp, typ); - } else { - return createErrorModuleDoesNotExist(imp, impName); - } } + var typ = tryResolveAsTypeNew(imp); + if (typ != null) { + return createResolvedType(imp, exp, typ); + } + var cons = tryResolveAsConstructor(imp); + if (cons != null) { + return createResolvedConstructor(imp, exp, cons); + } + var moduleMethod = tryResolveAsModuleMethod(imp); + if (moduleMethod != null) { + return createResolvedModuleMethod(imp, exp, moduleMethod); + } + var extensionMethods = tryResolveAsExtensionMethods(imp); + if (extensionMethods != null) { + return createResolvedExtensionMethods(imp, exp, extensionMethods); + } + var conversionMethods = tryResolveAsConversionMethods(imp); + if (conversionMethods != null) { + return createResolvedConversionMethods(imp, exp, conversionMethods); + } + return createErrorModuleDoesNotExist(imp, impName); } catch (IOException e) { return createErrorPackageCoundNotBeLoaded(imp, impName, e.getMessage()); } @@ -166,6 +165,96 @@ public abstract class ImportResolverAlgorithm< return type.orElse(null); } + private ResolvedConstructor tryResolveAsConstructor(Import imp) { + var parts = partsForImport(imp); + if (parts.size() < 3) { + return null; + } + var constructors = definedConstructors(imp); + if (constructors == null) { + return null; + } + var constrName = parts.get(parts.size() - 1); + var consOpt = + constructors.stream() + .filter(cons -> nameForConstructor(cons).equals(constrName)) + .findFirst(); + return consOpt.orElse(null); + } + + private ResolvedModuleMethod tryResolveAsModuleMethod(Import imp) { + var parts = partsForImport(imp); + if (parts.size() < 3) { + return null; + } + var moduleMethods = definedModuleMethods(imp); + if (moduleMethods == null) { + return null; + } + var methodName = parts.get(parts.size() - 1); + var methodOpt = + moduleMethods.stream() + .filter(method -> nameForModuleMethod(method).equals(methodName)) + .findFirst(); + return methodOpt.orElse(null); + } + + /** + * Tries to resolve the given import as a list of extension methods. Note that it is possible that + * a single symbol resolves to multiple extension methods. + * + * @return List with at least one element. null if there are no static methods in the imported + * module scope. + */ + private java.util.List tryResolveAsExtensionMethods(Import imp) { + var parts = partsForImport(imp); + if (parts.size() < 3) { + return null; + } + var definedExtensionMethods = definedExtensionMethods(imp); + if (definedExtensionMethods == null) { + return null; + } + var methodName = parts.get(parts.size() - 1); + var foundExtMethods = + definedExtensionMethods.stream() + .filter(method -> nameForExtensionMethod(method).equals(methodName)) + .collect(Collectors.toUnmodifiableList()); + if (foundExtMethods.isEmpty()) { + return null; + } else { + return foundExtMethods; + } + } + + /** + * Tries to resolve the given import as a list of conversion methods. Note that it is possible + * that a single symbol resolves to multiple extension methods. + * + * @return List of at least one element. null if there are no conversion methods in the imported + * module scope. + */ + private java.util.List tryResolveAsConversionMethods(Import imp) { + var parts = partsForImport(imp); + if (parts.size() < 3) { + return null; + } + var definedConvMethods = definedConversionMethods(imp); + if (definedConvMethods == null) { + return null; + } + var methodName = parts.get(parts.size() - 1); + var foundConvMethods = + definedConvMethods.stream() + .filter(method -> nameForConversionMethod(method).equals(methodName)) + .collect(Collectors.toUnmodifiableList()); + if (foundConvMethods.isEmpty()) { + return null; + } else { + return foundConvMethods; + } + } + public static final class HiddenNamesConflict extends RuntimeException { private HiddenNamesConflict(String message) { super(message); diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/phase/ImportResolverForIR.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/phase/ImportResolverForIR.java index 1416d35d2b..671be7c243 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/phase/ImportResolverForIR.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/phase/ImportResolverForIR.java @@ -3,6 +3,7 @@ package org.enso.compiler.phase; import java.io.IOException; import java.util.Objects; +import java.util.stream.Collectors; import org.enso.common.CompilationStage; import org.enso.compiler.Compiler; import org.enso.compiler.context.CompilerContext; @@ -12,6 +13,12 @@ import org.enso.compiler.core.ir.module.scope.Export; import org.enso.compiler.core.ir.module.scope.Import; import org.enso.compiler.data.BindingsMap; import org.enso.compiler.data.BindingsMap$ModuleReference$Concrete; +import org.enso.compiler.data.BindingsMap.ImportTarget; +import org.enso.compiler.data.BindingsMap.ResolvedConstructor; +import org.enso.compiler.data.BindingsMap.ResolvedConversionMethod; +import org.enso.compiler.data.BindingsMap.ResolvedImport; +import org.enso.compiler.data.BindingsMap.ResolvedModuleMethod; +import org.enso.compiler.data.BindingsMap.ResolvedExtensionMethod; import org.enso.compiler.data.BindingsMap.ResolvedType; import org.enso.editions.LibraryName; @@ -26,7 +33,11 @@ abstract class ImportResolverForIR extends ImportResolverAlgorithm< Import.Module, Export.Module, ResolvedType, - CompilerContext.Module + CompilerContext.Module, + ResolvedConstructor, + ResolvedModuleMethod, + ResolvedExtensionMethod, + ResolvedConversionMethod > { abstract Compiler getCompiler(); @@ -50,6 +61,26 @@ abstract class ImportResolverForIR extends ImportResolverAlgorithm< return e.qualifiedName().item(); } + @Override + protected String nameForConstructor(ResolvedConstructor cons) { + return cons.qualifiedName().item(); + } + + @Override + protected String nameForModuleMethod(ResolvedModuleMethod resolvedModuleMethod) { + return resolvedModuleMethod.methodName(); + } + + @Override + protected String nameForExtensionMethod(ResolvedExtensionMethod resolvedStaticMethod) { + return resolvedStaticMethod.methodName(); + } + + @Override + protected String nameForConversionMethod(ResolvedConversionMethod resolvedConversionMethod) { + return resolvedConversionMethod.methodName(); + } + @Override protected final java.util.List exportsFor(Module module, String impName) { java.util.List exp = CollectionConverters.SeqHasAsJava(module.exports()).asJava().stream().map(e -> switch (e) { @@ -59,11 +90,6 @@ abstract class ImportResolverForIR extends ImportResolverAlgorithm< return exp; } - @Override - protected final boolean isAll(Export.Module ex) { - return ex.isAll(); - } - @Override protected final java.util.List onlyNames(Export.Module ex) { if (ex.onlyNames().isEmpty()) { @@ -73,15 +99,6 @@ abstract class ImportResolverForIR extends ImportResolverAlgorithm< return list; } - @Override - protected final java.util.List hiddenNames(Export.Module ex) { - if (ex.hiddenNames().isEmpty()) { - return null; - } - var list = CollectionConverters.SeqHasAsJava(ex.hiddenNames().get().map(n -> n.name())).asJava(); - return list; - } - @Override protected final java.util.List definedEntities(Import.Module name) { var parts = partsForImport(name); @@ -94,16 +111,7 @@ abstract class ImportResolverForIR extends ImportResolverAlgorithm< } var mod = optionMod.get(); compiler.ensureParsed(mod); - var bindingsMap = mod.getBindingsMap(); - if (bindingsMap == null) { - compiler.context().updateModule(mod, u -> { - u.invalidateCache(); - u.ir(null); - u.compilationStage(CompilationStage.INITIAL); - }); - compiler.ensureParsed(mod, false); - bindingsMap = mod.getBindingsMap(); - } + var bindingsMap = loadBindingsMap(mod); var entitiesStream = bindingsMap.definedEntities().map(e -> switch (e) { case BindingsMap.Type t -> { assert e.name().equals(t.name()) : e.name() + " != " + t.name(); @@ -117,6 +125,156 @@ abstract class ImportResolverForIR extends ImportResolverAlgorithm< return entities; } + /** + * Returns list of constructors for the given import. + * @param imp The import is treated as an import of a constructor from a type. + * The last part is constructor, the second to last is type, + * the third to last is module. + * @return null if the import is not a constructor import. + */ + @Override + protected java.util.List definedConstructors(Import.Module imp) { + var parts = partsForImport(imp); + if (parts.size() < 3) { + return null; + } + var typeName = parts.get(parts.size() - 2); + var modFullName = parts + .stream() + .limit(parts.size() - 2) + .collect(Collectors.joining(".")); + var compiler = getCompiler(); + var optionMod = compiler.getModule(modFullName); + if (optionMod.isEmpty()) { + return null; + } + var mod = optionMod.get(); + compiler.ensureParsed(mod); + var bindingsMap = loadBindingsMap(mod); + var foundType = scala.jdk.javaapi.CollectionConverters.asJava(bindingsMap.definedEntities()) + .stream() + .map(e -> { + if (e instanceof BindingsMap.Type tp) { + return tp; + } + return null; + }) + .filter(Objects::nonNull) + .filter(tp -> tp.name().equals(typeName)) + .findFirst(); + if (foundType.isEmpty()) { + return null; + } + var tp = foundType.get(); + var resolvedType = new BindingsMap.ResolvedType( + new BindingsMap$ModuleReference$Concrete(mod), + tp + ); + return scala.jdk.javaapi.CollectionConverters.asJava(tp.members()) + .stream() + .map(cons -> new ResolvedConstructor(resolvedType, cons)) + .collect(Collectors.toUnmodifiableList()); + } + + @Override + protected java.util.List definedModuleMethods(Import.Module imp) { + var parts = partsForImport(imp); + if (parts.size() < 3) { + return null; + } + var modMethodNameIdx = parts.size() - 1; + var modMethodName = parts.get(modMethodNameIdx); + var modFullName = parts + .stream() + .limit(modMethodNameIdx) + .collect(Collectors.joining(".")); + var compiler = getCompiler(); + var optionMod = compiler.getModule(modFullName); + if (optionMod.isEmpty()) { + return null; + } + var mod = optionMod.get(); + compiler.ensureParsed(mod); + var bindingsMap = loadBindingsMap(mod); + var modMethods = scala.jdk.javaapi.CollectionConverters.asJava(bindingsMap.definedEntities()) + .stream() + .filter(definedEntity -> { + if (definedEntity instanceof BindingsMap.ModuleMethod moduleMethod) { + return moduleMethod.name().equals(modMethodName); + } + return false; + }) + .map(entity -> new ResolvedModuleMethod(new BindingsMap$ModuleReference$Concrete(mod), (BindingsMap.ModuleMethod) entity)) + .collect(Collectors.toUnmodifiableList()); + return modMethods; + } + + @Override + protected java.util.List definedExtensionMethods(Import.Module imp) { + var parts = partsForImport(imp); + if (parts.size() < 3) { + return null; + } + var modMethodNameIdx = parts.size() - 1; + var modMethodName = parts.get(modMethodNameIdx); + var modFullName = parts + .stream() + .limit(modMethodNameIdx) + .collect(Collectors.joining(".")); + var compiler = getCompiler(); + var optionMod = compiler.getModule(modFullName); + if (optionMod.isEmpty()) { + return null; + } + var mod = optionMod.get(); + compiler.ensureParsed(mod); + var bindingsMap = loadBindingsMap(mod); + var extensionMethods = scala.jdk.javaapi.CollectionConverters.asJava(bindingsMap.definedEntities()) + .stream() + .filter(definedEntity -> { + if (definedEntity instanceof BindingsMap.ExtensionMethod extensionMethod) { + return extensionMethod.name().equals(modMethodName); + } + return false; + }) + .map(entity -> new ResolvedExtensionMethod(new BindingsMap$ModuleReference$Concrete(mod), (BindingsMap.ExtensionMethod) entity)) + .collect(Collectors.toUnmodifiableList()); + return extensionMethods; + } + + @Override + protected java.util.List definedConversionMethods(Import.Module imp) { + var parts = partsForImport(imp); + if (parts.size() < 3) { + return null; + } + var modMethodNameIdx = parts.size() - 1; + var modMethodName = parts.get(modMethodNameIdx); + var modFullName = parts + .stream() + .limit(modMethodNameIdx) + .collect(Collectors.joining(".")); + var compiler = getCompiler(); + var optionMod = compiler.getModule(modFullName); + if (optionMod.isEmpty()) { + return null; + } + var mod = optionMod.get(); + compiler.ensureParsed(mod); + var bindingsMap = loadBindingsMap(mod); + var conversionMethods = scala.jdk.javaapi.CollectionConverters.asJava(bindingsMap.definedEntities()) + .stream() + .filter(definedEntity -> { + if (definedEntity instanceof BindingsMap.ConversionMethod conversionMethod) { + return conversionMethod.methodName().equals(modMethodName); + } + return false; + }) + .map(entity -> new ResolvedConversionMethod(new BindingsMap$ModuleReference$Concrete(mod), (BindingsMap.ConversionMethod) entity)) + .collect(Collectors.toUnmodifiableList()); + return conversionMethods; + } + @Override protected final CompilerContext.Module loadLibraryModule(LibraryName libraryName, String moduleName) throws IOException { var compiler = this.getCompiler(); @@ -136,14 +294,52 @@ abstract class ImportResolverForIR extends ImportResolverAlgorithm< @Override protected final Tuple2> createResolvedImport(Import.Module imp, java.util.List exp, CompilerContext.Module m) { - scala.Option someBinding = Option.apply(new BindingsMap.ResolvedImport(imp, toScalaList(exp), new BindingsMap.ResolvedModule(new BindingsMap$ModuleReference$Concrete(m)))); - return new Tuple2<>(imp, someBinding); + var resolvedModule = new BindingsMap.ResolvedModule(new BindingsMap$ModuleReference$Concrete(m)); + var resolvedImport = new BindingsMap.ResolvedImport(imp, toScalaList(exp), toScalaList(java.util.List.of(resolvedModule))); + return new Tuple2<>(imp, scala.Some.apply(resolvedImport)); } @Override protected final Tuple2> createResolvedType(Import.Module imp, java.util.List exp, BindingsMap.ResolvedType typ) { - scala.Option someBinding = Option.apply(new BindingsMap.ResolvedImport(imp, toScalaList(exp), typ)); - return new Tuple2<>(imp, someBinding); + var resolvedImport = new BindingsMap.ResolvedImport(imp, toScalaList(exp), toScalaList(java.util.List.of(typ))); + return new Tuple2<>(imp, scala.Some.apply(resolvedImport)); + } + + @Override + protected Tuple2> createResolvedConstructor(Import.Module imp, + java.util.List exp, ResolvedConstructor cons) { + var resolvedImport = new BindingsMap.ResolvedImport(imp, toScalaList(exp), toScalaList(java.util.List.of(cons))); + return new Tuple2<>(imp, scala.Some.apply(resolvedImport)); + } + + @Override + protected Tuple2> createResolvedModuleMethod(Import.Module imp, + java.util.List exp, ResolvedModuleMethod resolvedModuleMethod) { + var resolvedImport = new BindingsMap.ResolvedImport(imp, toScalaList(exp), toScalaList(java.util.List.of(resolvedModuleMethod))); + return new Tuple2<>(imp, scala.Some.apply(resolvedImport)); + } + + @Override + protected Tuple2> createResolvedExtensionMethods(Import.Module imp, + java.util.List exp, java.util.List extensionMethods) { + java.util.List importTargets = extensionMethods + .stream() + .map(ImportTarget.class::cast) + .collect(Collectors.toUnmodifiableList()); + var resolvedImport = new BindingsMap.ResolvedImport(imp, toScalaList(exp), toScalaList(importTargets)); + return new Tuple2<>(imp, scala.Some.apply(resolvedImport)); + } + + @Override + protected Tuple2> createResolvedConversionMethods( + Import.Module imp, java.util.List exp, + java.util.List resolvedConversionMethods) { + java.util.List importTargets = resolvedConversionMethods + .stream() + .map(ImportTarget.class::cast) + .collect(Collectors.toUnmodifiableList()); + var resolvedImport = new BindingsMap.ResolvedImport(imp, toScalaList(exp), toScalaList(importTargets)); + return new Tuple2<>(imp, scala.Some.apply(resolvedImport)); } @Override @@ -157,6 +353,20 @@ abstract class ImportResolverForIR extends ImportResolverAlgorithm< return new Tuple2<>(new ImportExport(imp, new ImportExport.ModuleDoesNotExist(impName), imp.passData(), imp.diagnostics()), Option.empty()); } + private BindingsMap loadBindingsMap(CompilerContext.Module mod) { + var bindingsMap = mod.getBindingsMap(); + if (bindingsMap == null) { + getCompiler().context().updateModule(mod, u -> { + u.invalidateCache(); + u.ir(null); + u.compilationStage(CompilationStage.INITIAL); + }); + getCompiler().ensureParsed(mod, false); + bindingsMap = mod.getBindingsMap(); + } + return bindingsMap; + } + private static List toScalaList(java.util.List qualifiedConflicts) { return CollectionConverters.ListHasAsScala(qualifiedConflicts).asScala().toList(); } diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/phase/exports/ConflictingResolutionsError.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/phase/exports/ConflictingResolutionsError.java new file mode 100644 index 0000000000..1f0ad66614 --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/phase/exports/ConflictingResolutionsError.java @@ -0,0 +1,62 @@ +package org.enso.compiler.phase.exports; + +import java.util.List; +import java.util.stream.Collectors; +import org.enso.compiler.core.CompilerError; +import org.enso.compiler.data.BindingsMap.ResolvedConstructor; +import org.enso.compiler.data.BindingsMap.ResolvedMethod; +import org.enso.compiler.data.BindingsMap.ResolvedModule; +import org.enso.compiler.data.BindingsMap.ResolvedName; +import org.enso.compiler.data.BindingsMap.ResolvedPolyglotField; +import org.enso.compiler.data.BindingsMap.ResolvedPolyglotSymbol; +import org.enso.compiler.data.BindingsMap.ResolvedType; +import org.enso.pkg.QualifiedName; + +/** + * An error thrown when there are multiple conflicting resolutions for a symbol. Note that, it is OK + * for example to have multiple extension methods resolved for a symbol. But not if one symbol + * resolves to a type and a module. + */ +public final class ConflictingResolutionsError extends CompilerError { + private ConflictingResolutionsError(String msg) { + super(msg); + } + + public static ConflictingResolutionsError create( + QualifiedName moduleName, List conflictingResolutions) { + assert conflictingResolutions.size() > 1; + var resolutionNames = + conflictingResolutions.stream() + .map(ConflictingResolutionsError::resolvedNameToString) + .collect(Collectors.toUnmodifiableList()); + var sb = new StringBuilder(); + sb.append("Conflicting resolutions in module '"); + sb.append(moduleName); + sb.append("': "); + sb.append(resolutionNames); + sb.append(". "); + sb.append( + "Probably caused by a name conflict of a defined entity in the module and an export."); + return new ConflictingResolutionsError(sb.toString()); + } + + private static String resolvedNameToString(ResolvedName resolvedName) { + return switch (resolvedName) { + case ResolvedType type -> "Type '" + type.qualifiedName() + "'"; + case ResolvedConstructor resolvedConstructor -> { + var typeName = resolvedConstructor.tpe().qualifiedName().toString(); + var consName = resolvedConstructor.cons().name(); + yield "Constructor '" + typeName + "." + consName + "'"; + } + case ResolvedMethod resolvedMethod -> "Method '" + resolvedMethod.qualifiedName() + "'"; + case ResolvedModule resolvedModule -> "Module '" + resolvedModule.qualifiedName() + "'"; + case ResolvedPolyglotField resolvedPolyglotField -> "Polyglot field '" + + resolvedPolyglotField.qualifiedName() + + "'"; + case ResolvedPolyglotSymbol resolvedPolyglotSymbol -> "Polyglot symbol '" + + resolvedPolyglotSymbol.qualifiedName() + + "'"; + default -> throw new UnsupportedOperationException("unimplemented: " + resolvedName); + }; + } +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/phase/exports/ExportSymbolAnalysis.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/phase/exports/ExportSymbolAnalysis.java new file mode 100644 index 0000000000..a6a59b7c44 --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/phase/exports/ExportSymbolAnalysis.java @@ -0,0 +1,270 @@ +package org.enso.compiler.phase.exports; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.enso.compiler.PackageRepository; +import org.enso.compiler.context.CompilerContext; +import org.enso.compiler.core.ir.Diagnostic; +import org.enso.compiler.core.ir.DiagnosticStorage; +import org.enso.compiler.core.ir.MetadataStorage; +import org.enso.compiler.core.ir.Module; +import org.enso.compiler.core.ir.Name; +import org.enso.compiler.core.ir.expression.errors.ImportExport; +import org.enso.compiler.core.ir.module.scope.Export; +import org.enso.compiler.data.BindingsMap.ResolvedType; +import org.enso.compiler.pass.IRPass; +import scala.collection.immutable.Seq; +import scala.collection.immutable.Seq$; +import scala.jdk.javaapi.CollectionConverters; + +/** + * Ensures that all the symbols that are exported exist. If not, an IR error is generated. It is not + * a {@link IRPass compiler pass} because it needs access to {@link + * org.enso.compiler.PackageRepository}. Ignores renamed exports. + */ +public final class ExportSymbolAnalysis { + private ExportSymbolAnalysis() {} + + /** + * For all the exports in the given IR of the module, ensures that all the symbols that are + * exported exists. If not, the corresponding IR is replaced with an {@link ImportExport error + * IR}. + * + * @param moduleIr IR of the module to be analyzed for existance of exported symbols. + * @return A copy of the given {@code moduleIr} optinally with some export IRs replaced with + * corresponding {@link ImportExport errors}. + */ + public static Module analyseModule(Module moduleIr, PackageRepository packageRepository) { + List exportErrors = new ArrayList<>(); + moduleIr + .exports() + .foreach( + export -> { + if (export instanceof Export.Module exportIr && exportIr.rename().isEmpty()) { + var exportNameParts = CollectionConverters.asJava(exportIr.name().parts()); + assert exportNameParts.size() > 2 + : "All the exports should already be desugared in the module discovery compiler" + + " passes"; + + if (exportNameParts.size() == 3) { + // There are 3 parts of the export, which means that it should be a direct + // module export like `export namespace.project.Module`, or + // `from namespace.project.Module export Symbol`. + var modFQN = + exportNameParts.stream().map(Name::name).collect(Collectors.joining(".")); + var mod = getLoadedModule(modFQN, packageRepository); + if (mod == null) { + var err = createModuleDoesNotExistError(exportIr, modFQN); + exportErrors.add(err); + return null; + } + if (exportIr.onlyNames().isDefined()) { + var symbols = + CollectionConverters.asJava(exportIr.onlyNames().get()).stream() + .map(Name.class::cast) + .toList(); + var errs = analyseSymbolsFromModule(mod, symbols); + exportErrors.addAll(errs); + } + return null; + } + + assert exportNameParts.size() > 3; + + String lastNameFQN; + String lastNameItem; + String preLastNameFQN; + List symbols; + if (exportIr.onlyNames().isDefined()) { + lastNameItem = exportNameParts.get(exportNameParts.size() - 1).name(); + lastNameFQN = + exportNameParts.stream().map(Name::name).collect(Collectors.joining(".")); + preLastNameFQN = + exportNameParts.stream() + .map(Name::name) + .limit(exportNameParts.size() - 1) + .collect(Collectors.joining(".")); + symbols = + CollectionConverters.asJava(exportIr.onlyNames().get()).stream() + .map(Name.class::cast) + .toList(); + } else { + lastNameItem = exportNameParts.get(exportNameParts.size() - 2).name(); + lastNameFQN = + exportNameParts.stream() + .map(Name::name) + .limit(exportNameParts.size() - 1) + .collect(Collectors.joining(".")); + preLastNameFQN = + exportNameParts.stream() + .map(Name::name) + .limit(exportNameParts.size() - 2) + .collect(Collectors.joining(".")); + symbols = List.of(exportNameParts.get(exportNameParts.size() - 1)); + } + + var mod = getLoadedModule(lastNameFQN, packageRepository); + if (mod != null) { + var errs = analyseSymbolsFromModule(mod, symbols); + if (!errs.isEmpty()) { + // It is also possible that symbol refers to a submodule of this module + var subModFQN = + exportNameParts.stream().map(Name::name).collect(Collectors.joining(".")); + var subMod = getLoadedModule(subModFQN, packageRepository); + if (subMod != null) { + // Everything is OK - we export the subMod directly + return null; + } + } + exportErrors.addAll(errs); + return null; + } + mod = getLoadedModule(preLastNameFQN, packageRepository); + if (mod == null) { + var err = createModuleDoesNotExistError(exportIr, preLastNameFQN); + exportErrors.add(err); + return null; + } + // lastNameFQN must be type in module `mod` + var modFQN = preLastNameFQN; + var typeName = lastNameItem; + var resolvedType = getResolvedTypeFromModule(mod, typeName); + if (resolvedType == null) { + var err = createTypeDoesNotExistError(exportIr, modFQN, typeName); + exportErrors.add(err); + return null; + } + var errs = analyseSymbolsFromType(resolvedType, symbols); + exportErrors.addAll(errs); + } + return null; + }); + + if (exportErrors.isEmpty()) { + return moduleIr; + } else { + return moduleIr.copy( + moduleIr.imports(), + CollectionConverters.asScala(exportErrors).toList(), + moduleIr.bindings(), + moduleIr.location(), + moduleIr.passData(), + moduleIr.diagnostics(), + moduleIr.id()); + } + } + + private static CompilerContext.Module getLoadedModule(String modFQN, PackageRepository pkgRepo) { + assert modFQN.contains(".") : "modFQN is a FQN"; + var modOpt = pkgRepo.getLoadedModule(modFQN); + if (modOpt.isDefined()) { + return modOpt.get(); + } else { + return null; + } + } + + private static ResolvedType getResolvedTypeFromModule( + CompilerContext.Module module, String typeName) { + assert !typeName.contains(".") : "Not a FQN"; + var typeOpt = module.getBindingsMap().exportedSymbols().get(typeName); + if (typeOpt.isEmpty()) { + return null; + } + var resolvedNames = typeOpt.get(); + if (resolvedNames.size() > 1) { + // We expect a single ResolvedType target + return null; + } + if (resolvedNames.head() instanceof ResolvedType resolvedType) { + return resolvedType; + } + return null; + } + + /** + * @return Optionally empty list of errors. Not null. + */ + private static List analyseSymbolsFromModule( + CompilerContext.Module module, List symbols) { + var bindingsMap = module.getBindingsMap(); + if (bindingsMap == null) { + // This can happen if the module was not yet compiled. Which may happen. + // In that case, just skip the check. + return List.of(); + } + var errors = new ArrayList(); + for (var symbol : symbols) { + var resolvedNamesOpt = bindingsMap.exportedSymbols().get(symbol.name()); + if (resolvedNamesOpt.isEmpty()) { + errors.add( + createSymbolDoesNotExistError(symbol, symbol.name(), module.getName().toString())); + } + } + return errors; + } + + /** + * @return Optionally empty list of errors. Not null. + */ + private static List analyseSymbolsFromType(ResolvedType type, List symbols) { + var errors = new ArrayList(); + var constructors = CollectionConverters.asJava(type.tp().members()); + for (var symbol : symbols) { + var symbolIsConstructor = + constructors.stream().anyMatch(cons -> cons.name().equals(symbol.name())); + if (!symbolIsConstructor) { + errors.add(createNoSuchConstructorError(symbol, type.tp().name(), symbol.name())); + } + } + return errors; + } + + private static ImportExport createModuleDoesNotExistError(Export.Module exportIr, String modFQN) { + assert modFQN.contains("."); + return ImportExport.apply( + exportIr, new ImportExport.ModuleDoesNotExist(modFQN), emptyPassData(), emptyDiagnostics()); + } + + private static ImportExport createSymbolDoesNotExistError( + Name symbolIr, String symbolName, String modFQN) { + assert modFQN.contains("."); + assert !symbolName.contains("."); + return ImportExport.apply( + symbolIr, + new ImportExport.SymbolDoesNotExist(symbolName, modFQN), + emptyPassData(), + emptyDiagnostics()); + } + + private static ImportExport createNoSuchConstructorError( + Name symbolIr, String typeName, String consName) { + assert !consName.contains("."); + return ImportExport.apply( + symbolIr, + new ImportExport.NoSuchConstructor(typeName, consName), + emptyPassData(), + emptyDiagnostics()); + } + + private static ImportExport createTypeDoesNotExistError( + Export.Module exportIr, String modFQN, String typeName) { + assert modFQN.contains("."); + assert !typeName.contains("."); + return ImportExport.apply( + exportIr, + new ImportExport.TypeDoesNotExist(typeName, modFQN), + emptyPassData(), + emptyDiagnostics()); + } + + private static MetadataStorage emptyPassData() { + return new MetadataStorage(); + } + + @SuppressWarnings("unchecked") + private static DiagnosticStorage emptyDiagnostics() { + return DiagnosticStorage.apply((Seq) Seq$.MODULE$.empty()); + } +} 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 d0b2791b95..6505af59af 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 @@ -25,15 +25,15 @@ import org.enso.compiler.core.EnsoParser import org.enso.compiler.data.CompilerConfig import org.enso.compiler.pass.PassManager import org.enso.compiler.pass.analyse._ -import org.enso.compiler.phase.{ - ExportCycleException, - ExportsResolution, - ImportResolver, - ImportResolverAlgorithm -} +import org.enso.compiler.phase.{ImportResolver, ImportResolverAlgorithm} import org.enso.editions.LibraryName import org.enso.pkg.QualifiedName import org.enso.common.CompilationStage +import org.enso.compiler.phase.exports.{ + ExportCycleException, + ExportSymbolAnalysis, + ExportsResolution +} import org.enso.syntax2.Tree import java.io.PrintStream @@ -479,7 +479,18 @@ class Compiler( // the symbol brought to the scope has not been properly resolved yet. val sortedCachedModules = new ExportsResolution(context).runSort(modulesImportedWithCachedBindings) - sortedCachedModules ++ requiredModules + val allSortedModules = sortedCachedModules ++ requiredModules + allSortedModules.foreach { mod => + val newModIr = + ExportSymbolAnalysis.analyseModule(mod.getIr, packageRepository) + context.updateModule( + mod, + updater => { + updater.ir(newModIr) + } + ) + } + allSortedModules } private def ensureParsedAndAnalyzed(module: Module): Unit = { @@ -749,9 +760,7 @@ class Compiler( Export.Module( m, rename = None, - isAll = false, onlyNames = None, - hiddenNames = None, location = None, isSynthetic = true ) 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 38492bedcc..39a098b243 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 @@ -56,7 +56,6 @@ class Passes( ) } else List()) ++ List( - ExportSymbolAnalysis.INSTANCE, ShadowedPatternFields, UnreachableMatchBranches, NestedPatternMatch, 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 472e777ce6..f865dd8ece 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 @@ -39,10 +39,6 @@ case class BindingsMap( */ var resolvedImports: List[ResolvedImport] = List() - /** Modules exported by [[currentModule]]. - */ - var resolvedExports: List[ExportedModule] = List() - /** Symbols exported by [[currentModule]]. */ var exportedSymbols: Map[String, List[ResolvedName]] = Map() @@ -69,7 +65,6 @@ case class BindingsMap( def toAbstract: BindingsMap = { val copy = this.copy(currentModule = currentModule.toAbstract) copy.resolvedImports = this.resolvedImports.map(_.toAbstract) - copy.resolvedExports = this.resolvedExports.map(_.toAbstract) copy.exportedSymbols = this.exportedSymbols.map { case (key, value) => key -> value.map(name => name.toAbstract) } @@ -97,17 +92,7 @@ case class BindingsMap( } } - val withExports: Option[BindingsMap] = withImports.flatMap { bindings => - val newExports = this.resolvedExports.map(_.toConcrete(moduleMap)) - if (newExports.exists(_.isEmpty)) { - None - } else { - bindings.resolvedExports = newExports.map(_.get) - Some(bindings) - } - } - - val withSymbols: Option[BindingsMap] = withExports.flatMap { bindings => + val withSymbols: Option[BindingsMap] = withImports.flatMap { bindings => val newSymbols = this.exportedSymbols.map { case (key, value) => val newValue = value.map(_.toConcrete(moduleMap)) if (newValue.exists(_.isEmpty)) { @@ -143,7 +128,7 @@ case class BindingsMap( ): List[ResolvedName] = { resolvedImports .filter(i => importMatchesName(i, name) && !i.isSynthetic()) - .map(_.target) + .flatMap(_.targets) } private def importMatchesName(imp: ResolvedImport, name: String): Boolean = { @@ -161,9 +146,9 @@ case class BindingsMap( val resolvedNames = resolvedImports .flatMap { imp => if (imp.importDef.allowsAccess(name)) { - imp.target + imp .findExportedSymbolsFor(name) - .map((_, imp.isSynthetic(), imp.target)) + .map((_, imp.isSynthetic(), imp.targets)) } else { List() } } // synthetic imports should not be reported in the ambiguity reports @@ -305,49 +290,47 @@ case class BindingsMap( } /** Dumps the export statements from this module into a structure ready for - * further analysis. + * further analysis. It uses only [[resolvedImports]] field, as + * [[exportedSymbols]] fields are expected to be filled later. + * + * For every symbol that is exported from this bindings map, gathers the module + * in which the symbol is defined and returns it in the list. For example, if there + * is an export `export project.Module.method`, there will be `Module` in the returned list. * * @return a list of triples of the exported module, the name it is exported * as and any further symbol restrictions. */ def getDirectlyExportedModules: List[ExportedModule] = - resolvedImports.collect { case ResolvedImport(_, exports, mod) => - exports.map { exp => - val restriction = if (exp.isAll) { - if (exp.onlyNames.isDefined) { - SymbolRestriction.Only( - exp.onlyNames.get - .map(name => - SymbolRestriction - .AllowedResolution(name.name.toLowerCase, None) - ) - .toSet - ) - } else if (exp.hiddenNames.isDefined) { - SymbolRestriction.Hiding( - exp.hiddenNames.get.map(_.name.toLowerCase).toSet - ) - } else { - SymbolRestriction.All + resolvedImports + .collect { case ResolvedImport(_, exports, targets) => + exports.flatMap { exp => + val exportAs = exp.rename match { + case Some(rename) => Some(rename.name) + case None => None + } + val symbols = exp.onlyNames match { + case Some(onlyNames) => + onlyNames.map(_.name) + case None => + List(exp.name.parts.last.name) + } + targets.map { + case m: ResolvedModule => ExportedModule(m, exportAs, symbols) + case ResolvedType(modRef, _) => + ExportedModule(ResolvedModule(modRef), exportAs, symbols) + case ResolvedConstructor(ResolvedType(modRef, _), _) => + ExportedModule(ResolvedModule(modRef), exportAs, symbols) + case ResolvedModuleMethod(modRef, _) => + ExportedModule(ResolvedModule(modRef), exportAs, symbols) + case ResolvedExtensionMethod(modRef, _) => + ExportedModule(ResolvedModule(modRef), exportAs, symbols) + case ResolvedConversionMethod(modRef, _) => + ExportedModule(ResolvedModule(modRef), exportAs, symbols) } - } else { - SymbolRestriction.Only( - Set( - SymbolRestriction.AllowedResolution( - exp.getSimpleName.name.toLowerCase, - Some(mod) - ) - ) - ) } - val rename = if (!exp.isAll) { - Some(exp.getSimpleName.name) - } else { - None - } - ExportedModule(mod, rename, restriction) } - }.flatten + .flatten + .distinct } object BindingsMap { @@ -376,328 +359,36 @@ object BindingsMap { } } - /** Represents a symbol restriction on symbols exported from a module. */ - sealed trait SymbolRestriction { - - /** Whether the export statement allows accessing the given name. - * - * @param symbol the name to check - * @param resolution the particular resolution of `symbol` - * @return whether access to the symbol is permitted by this restriction. - */ - def canAccess(symbol: String, resolution: ResolvedName): Boolean - - /** Performs static optimizations on the restriction, simplifying - * common patterns. - * - * @return a possibly simpler version of the restriction, describing - * the same set of names. - */ - def optimize: SymbolRestriction - - /** Convert any internal [[ModuleReference]]s to abstract references. - * - * @return `this` with any module references made abstract - */ - def toAbstract: SymbolRestriction - - /** Convert any internal [[ModuleReference]]s to concrete references. - * - * @param moduleMap the mapping from qualified names to modules - * @return `this` with its module reference made concrete - */ - def toConcrete(moduleMap: ModuleMap): Option[SymbolRestriction] - } - - object SymbolRestriction { - - /** A representation of allowed symbol. An allowed symbol consists of - * a name and an optional resolution refinement. - * - * @param symbol the symbol name - * @param resolution the only allowed resolution of `symbol` - */ - case class AllowedResolution( - symbol: String, - resolution: Option[ResolvedName] - ) { - - /** Checks if the `symbol` is visible under this restriction, with - * a given resolution. - * - * @param symbol the symbol - * @param resolution `symbol`'s resolution - * @return `true` if the symbol is visible, `false` otherwise - */ - def allows(symbol: String, resolution: ResolvedName): Boolean = { - val symbolMatch = this.symbol == symbol.toLowerCase - val resolutionMatch = - this.resolution.isEmpty || this.resolution.get == resolution - symbolMatch && resolutionMatch - } - - /** Convert the internal resolution to abstract form. - * - * @return `this` with its resolution converted to abstract form - */ - def toAbstract: AllowedResolution = { - this.copy(resolution = resolution.map(_.toAbstract)) - } - - /** Convert the internal resolution to concrete form. - * - * @param moduleMap the mapping from qualified names to modules - * @return `this` with its resolution made concrete - */ - def toConcrete(moduleMap: ModuleMap): Option[AllowedResolution] = { - resolution match { - case None => Some(this) - case Some(res) => - res.toConcrete(moduleMap).map(r => this.copy(resolution = Some(r))) - } - } - } - - /** A restriction representing a set of allowed symbols. - * - * @param symbols the allowed symbols. - */ - case class Only(symbols: Set[AllowedResolution]) extends SymbolRestriction { - - /** @inheritdoc */ - override def canAccess( - symbol: String, - resolution: ResolvedName - ): Boolean = symbols.exists(_.allows(symbol, resolution)) - - /** @inheritdoc */ - override def optimize: SymbolRestriction = this - - /** @inheritdoc */ - override def toAbstract: Only = { - this.copy(symbols = symbols.map(_.toAbstract)) - } - - /** @inheritdoc */ - //noinspection DuplicatedCode - override def toConcrete(moduleMap: ModuleMap): Option[Only] = { - val newSymbols = symbols.map(_.toConcrete(moduleMap)) - if (!newSymbols.exists(_.isEmpty)) { - Some(this.copy(symbols = newSymbols.map(_.get))) - } else None - } - } - - /** A restriction representing a set of excluded symbols. - * - * @param symbols the excluded symbols. - */ - case class Hiding(symbols: Set[String]) extends SymbolRestriction { - - /** @inheritdoc */ - override def canAccess( - symbol: String, - resolution: ResolvedName - ): Boolean = !symbols.contains(symbol.toLowerCase) - - /** @inheritdoc */ - override def optimize: Hiding = this - - /** @inheritdoc */ - override def toAbstract: Hiding = this - - /** @inheritdoc */ - override def toConcrete(moduleMap: ModuleMap): Option[Hiding] = Some(this) - } - - /** A restriction meaning there's no restriction at all. - */ - case object All extends SymbolRestriction { - - /** @inheritdoc */ - override def canAccess( - symbol: String, - resolution: ResolvedName - ): Boolean = true - - /** @inheritdoc */ - override def optimize: SymbolRestriction = this - - /** @inheritdoc */ - override def toAbstract: All.type = this - - /** @inheritdoc */ - override def toConcrete(moduleMap: ModuleMap): Option[All.type] = Some( - this - ) - } - - /** A complete restriction – no symbols are permitted - */ - case object Empty extends SymbolRestriction { - - /** @inheritdoc */ - override def canAccess( - symbol: String, - resolution: ResolvedName - ): Boolean = false - - /** @inheritdoc */ - override def optimize: SymbolRestriction = this - - /** @inheritdoc */ - override def toAbstract: Empty.type = this - - /** @inheritdoc */ - override def toConcrete(moduleMap: ModuleMap): Option[Empty.type] = Some( - this - ) - } - - /** An intersection of restrictions – a symbol is allowed if all components - * allow it. - * - * @param restrictions the intersected restrictions. - */ - case class Intersect(restrictions: List[SymbolRestriction]) - extends SymbolRestriction { - - /** @inheritdoc */ - override def canAccess( - symbol: String, - resolution: ResolvedName - ): Boolean = restrictions.forall(_.canAccess(symbol, resolution)) - - /** @inheritdoc */ - //noinspection DuplicatedCode - override def optimize: SymbolRestriction = { - val optimizedTerms = restrictions.map(_.optimize) - val (intersects, otherTerms) = - optimizedTerms.partition(_.isInstanceOf[Intersect]) - val allTerms = intersects.flatMap( - _.asInstanceOf[Intersect].restrictions - ) ++ otherTerms - if (allTerms.contains(Empty)) { - return Empty - } - val unions = allTerms.filter(_.isInstanceOf[Union]) - val onlys = allTerms.collect { case only: Only => only } - val hides = allTerms.collect { case hiding: Hiding => hiding } - val combinedOnlys = onlys match { - case List() => None - case items => - Some(Only(items.map(_.symbols).reduce(_.intersect(_)))) - } - val combinedHiding = hides match { - case List() => None - case items => - Some(Hiding(items.map(_.symbols).reduce(_.union(_)))) - } - val newTerms = combinedHiding.toList ++ combinedOnlys.toList ++ unions - newTerms match { - case List() => All - case List(it) => it - case items => Intersect(items) - } - } - - /** @inheritdoc */ - override def toAbstract: Intersect = { - this.copy(restrictions = restrictions.map(_.toAbstract)) - } - - /** @inheritdoc */ - //noinspection DuplicatedCode - override def toConcrete(moduleMap: ModuleMap): Option[Intersect] = { - val newRestrictions = restrictions.map(_.toConcrete(moduleMap)) - if (!newRestrictions.exists(_.isEmpty)) { - Some(this.copy(restrictions = newRestrictions.map(_.get))) - } else None - } - } - - /** A union of restrictions – a symbol is allowed if any component allows - * it. - * - * @param restrictions the component restricitons. - */ - case class Union(restrictions: List[SymbolRestriction]) - extends SymbolRestriction { - - /** @inheritdoc */ - override def canAccess( - symbol: String, - resolution: ResolvedName - ): Boolean = restrictions.exists(_.canAccess(symbol, resolution)) - - /** @inheritdoc */ - //noinspection DuplicatedCode - override def optimize: SymbolRestriction = { - val optimizedTerms = restrictions.map(_.optimize) - val (unions, otherTerms) = - optimizedTerms.partition(_.isInstanceOf[Union]) - val allTerms = unions.flatMap( - _.asInstanceOf[Union].restrictions - ) ++ otherTerms - if (allTerms.contains(All)) { - return All - } - val intersects = allTerms.filter(_.isInstanceOf[Intersect]) - val onlys = allTerms.collect { case only: Only => only } - val hides = allTerms.collect { case hiding: Hiding => hiding } - val combinedOnlys = onlys match { - case List() => None - case items => - Some(Only(items.map(_.symbols).reduce(_.union(_)))) - } - val combinedHiding = hides match { - case List() => None - case items => - Some(Hiding(items.map(_.symbols).reduce(_.intersect(_)))) - } - val newTerms = - combinedHiding.toList ++ combinedOnlys.toList ++ intersects - newTerms match { - case List() => Empty - case List(it) => it - case items => Union(items) - } - } - - /** @inheritdoc */ - override def toAbstract: Union = { - this.copy(restrictions = restrictions.map(_.toAbstract)) - } - - /** @inheritdoc */ - //noinspection DuplicatedCode - override def toConcrete(moduleMap: ModuleMap): Option[Union] = { - val newRestrictions = restrictions.map(_.toConcrete(moduleMap)) - if (!newRestrictions.exists(_.isEmpty)) { - Some(this.copy(restrictions = newRestrictions.map(_.get))) - } else None - } - } - } - /** A representation of a resolved export statement. * - * @param target the module being exported. + * @param module the target being exported. * @param exportedAs the name it is exported as. - * @param symbols any symbol restrictions connected to the export. + * @param symbols List of symbols connected to the export. The symbol refers to the last part + * of the physical name of the target being exported. It is not a fully qualified + * name. */ case class ExportedModule( - target: ImportTarget, + module: ResolvedModule, exportedAs: Option[String], - symbols: SymbolRestriction + symbols: List[String] ) { + assert( + symbols.forall(!_.contains(".")), + "Not expected fully qualified names as symbols" + ) + if (exportedAs.isDefined) { + assert( + !exportedAs.get.contains("."), + "Not expected fully qualified name as `exportedAs`" + ) + } /** Convert the internal [[ModuleReference]] to an abstract reference. * * @return `this` with its module reference made abstract */ def toAbstract: ExportedModule = { - this.copy(target = target.toAbstract, symbols = symbols.toAbstract) + this.copy(module = module.toAbstract) } /** Convert the internal [[ModuleReference]] to a concrete reference. @@ -706,10 +397,8 @@ object BindingsMap { * @return `this` with its module reference made concrete */ def toConcrete(moduleMap: ModuleMap): Option[ExportedModule] = { - target.toConcrete(moduleMap).flatMap { x => - symbols - .toConcrete(moduleMap) - .map(y => this.copy(target = x, symbols = y)) + module.toConcrete(moduleMap).map { target => + this.copy(module = target) } } } @@ -739,20 +428,26 @@ object BindingsMap { * * @param importDef the definition of the import * @param exports the exports associated with the import - * @param target the module or type this import resolves to + * @param targets list of targets that this import resolves to. Note that it is valid for a single + * import to resolve to multiple entities, for example, in case of extension methods. */ case class ResolvedImport( importDef: ir.module.scope.Import.Module, exports: List[ir.module.scope.Export.Module], - target: ImportTarget + targets: List[ImportTarget] ) { + assert(targets.nonEmpty) + assert( + areTargetsConsistent(), + "All targets must be either static methods or conversion methods" + ) /** Convert the internal [[ModuleReference]] to an abstract reference. * * @return `this` with its module reference made abstract */ def toAbstract: ResolvedImport = { - this.copy(target = target.toAbstract) + this.copy(targets = targets.map(_.toAbstract)) } /** Convert the internal [[ModuleReference]] to a concrete reference. @@ -761,7 +456,12 @@ object BindingsMap { * @return `this` with its module reference made concrete */ def toConcrete(moduleMap: ModuleMap): Option[ResolvedImport] = { - target.toConcrete(moduleMap).map(x => this.copy(target = x)) + val newTargets = targets.map(_.toConcrete(moduleMap)) + if (newTargets.forall(_.isDefined)) { + Some(this.copy(targets = newTargets.map(_.get))) + } else { + None + } } /** Determines if this resolved import statement was generated by the compiler. @@ -771,6 +471,24 @@ object BindingsMap { def isSynthetic(): Boolean = { importDef.isSynthetic } + + def findExportedSymbolsFor(name: String): List[ResolvedName] = { + targets.flatMap(_.findExportedSymbolsFor(name)) + } + + private def areTargetsConsistent(): Boolean = { + if (targets.size > 1) { + // If there are multiple targets, they can either all be static methods, or all be + // conversion methods. + val allStaticMethods = + targets.forall(_.isInstanceOf[ResolvedExtensionMethod]) + val allConversionMethods = + targets.forall(_.isInstanceOf[ResolvedConversionMethod]) + allStaticMethods || allConversionMethods + } else { + true + } + } } sealed trait DefinedEntity { @@ -778,8 +496,8 @@ object BindingsMap { def resolvedIn(module: ModuleReference): ResolvedName = this match { case t: Type => ResolvedType(module, t) - case staticMethod: StaticMethod => - ResolvedStaticMethod(module, staticMethod) + case staticMethod: ExtensionMethod => + ResolvedExtensionMethod(module, staticMethod) case conversionMethod: ConversionMethod => ResolvedConversionMethod(module, conversionMethod) case m: ModuleMethod => ResolvedModuleMethod(module, m) @@ -893,7 +611,7 @@ object BindingsMap { * My_Type.method = 42 * ``` */ - case class StaticMethod( + case class ExtensionMethod( methodName: String, tpName: String ) extends Method { @@ -973,7 +691,8 @@ object BindingsMap { * @param cons a representation of the constructor. */ case class ResolvedConstructor(tpe: ResolvedType, cons: Cons) - extends ResolvedName { + extends ResolvedName + with ImportTarget { /** @inheritdoc */ override def toAbstract: ResolvedConstructor = { @@ -993,6 +712,18 @@ object BindingsMap { /** @inheritdoc */ override def module: ModuleReference = tpe.module + + override def findExportedSymbolsFor(name: String): List[ResolvedName] = { + if (name == cons.name) { + List(this) + } else { + List() + } + } + + override def exportedSymbols: Map[String, List[ResolvedName]] = { + Map(cons.name -> List(this)) + } } /** A representation of a name being resolved to a module. @@ -1031,8 +762,24 @@ object BindingsMap { .exportedSymbols } - sealed trait ResolvedMethod extends ResolvedName { + sealed trait ResolvedMethod extends ResolvedName with ImportTarget { def methodName: String + + override def findExportedSymbolsFor(name: String): List[ResolvedName] = { + if (name == methodName) { + List(this) + } else { + List() + } + } + + override def exportedSymbols: Map[String, List[ResolvedName]] = Map( + methodName -> List(this) + ) + + override def qualifiedName: QualifiedName = { + module.getName.createChild(methodName) + } } /** A representation of a resolved method defined directly on module. @@ -1074,35 +821,27 @@ object BindingsMap { def unsafeGetIr(missingMessage: String): ir.module.scope.Definition = getIr.getOrElse(throw new CompilerError(missingMessage)) - override def qualifiedName: QualifiedName = - module.getName.createChild(method.name) - override def methodName: String = method.name } /** Method resolved on a type - either static method or extension method. */ - case class ResolvedStaticMethod( + case class ResolvedExtensionMethod( module: ModuleReference, - staticMethod: StaticMethod + staticMethod: ExtensionMethod ) extends ResolvedMethod { - override def toAbstract: ResolvedStaticMethod = { + override def toAbstract: ResolvedExtensionMethod = { this.copy(module = module.toAbstract) } override def toConcrete( moduleMap: ModuleMap - ): Option[ResolvedStaticMethod] = { + ): Option[ResolvedExtensionMethod] = { module.toConcrete(moduleMap).map { module => this.copy(module = module) } } - override def qualifiedName: QualifiedName = - module.getName - .createChild(staticMethod.tpName) - .createChild(staticMethod.methodName) - override def methodName: String = staticMethod.methodName } @@ -1122,11 +861,6 @@ object BindingsMap { } } - override def qualifiedName: QualifiedName = - module.getName - .createChild(conversionMethod.targetTpName) - .createChild(conversionMethod.methodName) - override def methodName: String = conversionMethod.methodName } @@ -1197,7 +931,7 @@ object BindingsMap { s" The imported polyglot field ${name};" case BindingsMap.ResolvedModuleMethod(module, symbol) => s" The method ${symbol.name} defined in module ${module.getName}" - case BindingsMap.ResolvedStaticMethod(module, staticMethod) => + case BindingsMap.ResolvedExtensionMethod(module, staticMethod) => s" The static method ${staticMethod.methodName} defined in module ${module.getName} for type ${staticMethod.tpName}" case BindingsMap.ResolvedConversionMethod(module, conversionMethod) => s" The conversion method ${conversionMethod.targetTpName}.${conversionMethod.methodName} defined in module ${module.getName}" diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.scala index 5014c99674..3cea240341 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.scala @@ -103,32 +103,34 @@ case object AmbiguousImportsAnalysis extends IRPass { _, _ ) => - getImportTarget(moduleImport, bindingMap) match { - case Some(importTarget) => + getImportTargets(moduleImport, bindingMap) match { + case Some(importTargets) => val encounteredErrors: ListBuffer[errors.ImportExport] = ListBuffer() onlyNames.foreach { symbol => val symbolName = symbol.name - importTarget.resolveExportedSymbol(symbolName) match { - case Right(resolvedNames) => - resolvedNames.foreach { resolvedName => - val symbolPath = resolvedName.qualifiedName.toString - tryAddEncounteredSymbol( - encounteredSymbols, - moduleImport, - symbolName, - symbolPath, - Some(resolvedName) - ) match { - case Left(error) => - encounteredErrors += error - case Right(_) => () + importTargets.foreach { importTarget => + importTarget.resolveExportedSymbol(symbolName) match { + case Right(resolvedNames) => + resolvedNames.foreach { resolvedName => + val symbolPath = resolvedName.qualifiedName.toString + tryAddEncounteredSymbol( + encounteredSymbols, + moduleImport, + symbolName, + symbolPath, + Some(resolvedName) + ) match { + case Left(error) => + encounteredErrors += error + case Right(_) => () + } } - } - case Left(resolutionError) => - throw new CompilerError( - s"Unreachable: (should have been resolved in previous passes) $resolutionError" - ) + case Left(resolutionError) => + throw new CompilerError( + s"Unreachable: (should have been resolved in previous passes) $resolutionError" + ) + } } } if (encounteredErrors.nonEmpty) { @@ -153,11 +155,11 @@ case object AmbiguousImportsAnalysis extends IRPass { _, _ ) => - getImportTarget(moduleImport, bindingMap) match { - case Some(importTarget) => + getImportTargets(moduleImport, bindingMap) match { + case Some(importTargets) => // Names of the symbols that are exported by a module or a type referred to via importTarget val exportedSymbolNames: List[String] = - importTarget.exportedSymbols.keySet.toList + importTargets.flatMap(_.exportedSymbols.keySet.toList) val symbolsToIterate = hiddenNames match { case None => exportedSymbolNames case Some(hiddenNamesLiterals) => @@ -167,25 +169,27 @@ case object AmbiguousImportsAnalysis extends IRPass { val encounteredErrors: ListBuffer[errors.ImportExport] = ListBuffer() symbolsToIterate.foreach { symbolName => - importTarget.resolveExportedSymbol(symbolName) match { - case Left(resolutionError) => - throw new CompilerError( - s"Unreachable: (should have been resolved in previous passes) $resolutionError" - ) - case Right(List(resolvedName)) => - tryAddEncounteredSymbol( - encounteredSymbols, - moduleImport, - symbolName, - resolvedName.qualifiedName.toString, - Some(resolvedName) - ) match { - case Left(error) => - encounteredErrors += error - case Right(_) => () - } - // If the symbolName is resolved to multiple objects, we ignore it. - case Right(_) => () + importTargets.foreach { importTarget => + importTarget.resolveExportedSymbol(symbolName) match { + case Left(resolutionError) => + throw new CompilerError( + s"Unreachable: (should have been resolved in previous passes) $resolutionError" + ) + case Right(List(resolvedName)) => + tryAddEncounteredSymbol( + encounteredSymbols, + moduleImport, + symbolName, + resolvedName.qualifiedName.toString, + Some(resolvedName) + ) match { + case Left(error) => + encounteredErrors += error + case Right(_) => () + } + // If the symbolName is resolved to multiple objects, we ignore it. + case Right(_) => () + } } } if (encounteredErrors.nonEmpty) { @@ -267,13 +271,13 @@ case object AmbiguousImportsAnalysis extends IRPass { } } - private def getImportTarget( + private def getImportTargets( imp: Import, bindingMap: BindingsMap - ): Option[BindingsMap.ImportTarget] = { + ): Option[List[BindingsMap.ImportTarget]] = { bindingMap.resolvedImports.find(_.importDef == imp) match { case Some(resolvedImport) => - Some(resolvedImport.target) + Some(resolvedImport.targets) case None => None } diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala index ecc72411c3..053ddede57 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala @@ -10,8 +10,8 @@ import org.enso.compiler.core.ir.MetadataStorage.MetadataPair import org.enso.compiler.data.BindingsMap import org.enso.compiler.data.BindingsMap.{ ConversionMethod, - ModuleMethod, - StaticMethod + ExtensionMethod, + ModuleMethod } import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.desugar.{ @@ -77,7 +77,7 @@ case object BindingAnalysis extends IRPass { Some(ModuleMethod(ref.methodName.name)) else { Some( - StaticMethod(ref.methodName.name, n.name) + ExtensionMethod(ref.methodName.name, n.name) ) } case Some(literal: Name.Literal) => @@ -86,7 +86,7 @@ case object BindingAnalysis extends IRPass { Some(ModuleMethod(ref.methodName.name)) else { Some( - StaticMethod(ref.methodName.name, literal.name) + ExtensionMethod(ref.methodName.name, literal.name) ) } case None => Some(ModuleMethod(ref.methodName.name)) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/ImportSymbolAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/ImportSymbolAnalysis.scala index 567d1c9add..409227f792 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/ImportSymbolAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/ImportSymbolAnalysis.scala @@ -68,15 +68,16 @@ case object ImportSymbolAnalysis extends IRPass { ) => bindingMap.resolvedImports.find(_.importDef == imp) match { case Some(resolvedImport) => - val importedTarget = resolvedImport.target - val unresolvedSymbols = + val importedTargets = resolvedImport.targets + val unresolvedSymbols = importedTargets.flatMap { importedTarget => onlyNames.filterNot(isSymbolResolved(importedTarget, _)) + } if (unresolvedSymbols.nonEmpty) { unresolvedSymbols .map( createErrorForUnresolvedSymbol( imp, - importedTarget, + importedTargets.head, _ ) ) @@ -85,10 +86,81 @@ case object ImportSymbolAnalysis extends IRPass { } case None => List(imp) } + // Importing symbols from methods is not allowed. The following code checks that if the + // import is importing all from a method, an error is reported. + case imp @ Import.Module( + _, + _, + isAll, + _, + _, + _, + isSynthetic, + _, + _ + ) if isAll && !isSynthetic => + bindingMap.resolvedImports.find(_.importDef == imp) match { + case Some(resolvedImport) => + val importedTargets = resolvedImport.targets + val errors = importedTargets.flatMap { importedTarget => + importedTarget match { + case BindingsMap.ResolvedModuleMethod(module, method) => + val err = createImportFromMethodError( + imp, + module.getName.toString, + method.name + ) + Some(err) + case BindingsMap.ResolvedExtensionMethod( + module, + staticMethod + ) => + val err = createImportFromMethodError( + imp, + module.getName.createChild(staticMethod.tpName).toString, + staticMethod.methodName + ) + Some(err) + case BindingsMap.ResolvedConversionMethod( + module, + conversionMethod + ) => + val err = createImportFromMethodError( + imp, + module.getName + .createChild(conversionMethod.targetTpName) + .toString, + conversionMethod.methodName + ) + Some(err) + case _ => None + } + } + if (errors.nonEmpty) { + errors + } else { + List(imp) + } + case None => List(imp) + } case _ => List(imp) } } + private def createImportFromMethodError( + imp: Import, + moduleName: String, + methodName: String + ): errors.ImportExport = { + errors.ImportExport( + imp, + errors.ImportExport.IllegalImportFromMethod( + moduleName, + methodName + ) + ) + } + private def createErrorForUnresolvedSymbol( imp: Import, importTarget: BindingsMap.ImportTarget, @@ -111,6 +183,40 @@ case object ImportSymbolAnalysis extends IRPass { unresolvedSymbol.name ) ) + case BindingsMap.ResolvedConstructor(_, cons) => + errors.ImportExport( + imp, + errors.ImportExport.NoSuchConstructor( + cons.name, + unresolvedSymbol.name + ) + ) + case BindingsMap.ResolvedModuleMethod(_, method) => + errors.ImportExport( + imp, + errors.ImportExport.NoSuchModuleMethod( + method.name, + unresolvedSymbol.name + ) + ) + case BindingsMap.ResolvedExtensionMethod(mod, staticMethod) => + errors.ImportExport( + imp, + errors.ImportExport.NoSuchStaticMethod( + moduleName = mod.getName.toString, + typeName = staticMethod.tpName, + methodName = unresolvedSymbol.name + ) + ) + case BindingsMap.ResolvedConversionMethod(mod, conversionMethod) => + errors.ImportExport( + imp, + errors.ImportExport.NoSuchConversionMethod( + moduleName = mod.getName.toString, + targetTypeName = conversionMethod.targetTpName, + sourceTypeName = conversionMethod.sourceTpName + ) + ) } } diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/desugar/Imports.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/desugar/Imports.scala index 1d6b79cba4..0f48b7cd21 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/desugar/Imports.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/desugar/Imports.scala @@ -77,7 +77,7 @@ case object Imports extends IRPass { name = newName.copy(parts = parts :+ mainModuleName), rename = computeRename( ex.rename, - ex.onlyNames.nonEmpty || ex.isAll, + ex.onlyNames.nonEmpty, parts(1).asInstanceOf[Name.Literal] ) ) @@ -110,10 +110,10 @@ case object Imports extends IRPass { private def computeRename( originalRename: Option[Name.Literal], - onlyNamesOrAll: Boolean, + onlyNames: Boolean, qualName: Name.Literal ): Option[Name.Literal] = - originalRename.orElse(Option.unless(onlyNamesOrAll)(qualName)) + originalRename.orElse(Option.unless(onlyNames)(qualName)) val currentProjectAlias = "project" diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/lint/ModuleNameConflicts.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/lint/ModuleNameConflicts.scala index b0f417ef81..4d85d84719 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/lint/ModuleNameConflicts.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/lint/ModuleNameConflicts.scala @@ -39,8 +39,6 @@ case object ModuleNameConflicts extends IRPass { case mod @ Export.Module( _, _, - false, - None, None, None, true, diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala index 1d60a44120..e1b63df05b 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala @@ -16,7 +16,6 @@ import org.enso.compiler.core.ir.expression.warnings import org.enso.compiler.core.ir.MetadataStorage.MetadataPair import org.enso.compiler.data.BindingsMap import org.enso.compiler.data.BindingsMap.{ - ExportedModule, ModuleReference, Resolution, ResolvedType @@ -92,47 +91,45 @@ case object FullyQualifiedNames extends IRPass { // `Standard.Base.Error.Foo`, will always lead to name conflicts with // the exported type `Error`. if (isMainModule(moduleContext)) { - scopeMap.resolvedExports.foreach { - case ExportedModule( - resolution @ ResolvedType(exportedModuleRef, tpe), - exportedAs, - _ - ) => - val tpeName = exportedAs.getOrElse(tpe.name) - val exportedModule = exportedModuleRef.unsafeAsModule() - if ( - exportedModuleRef.getName.path.length == 2 && exportedModuleRef.getName.item == tpeName && !exportedModule.isSynthetic - ) { - val allStarting = moduleContext.pkgRepo - .map( - _.getLoadedModules.filter(m => - exportedModuleRef.getName != m.getName && m - .getName() - .toString - .startsWith(exportedModuleRef.getName.toString + ".") + scopeMap.exportedSymbols.foreach { case (symbolName, resolvedNames) => + resolvedNames.foreach { + case resolution @ ResolvedType(exportedModuleRef, _) => + val tpeName = symbolName + val exportedModule = exportedModuleRef.unsafeAsModule() + if ( + exportedModuleRef.getName.path.length == 2 && exportedModuleRef.getName.item == tpeName && !exportedModule.isSynthetic + ) { + val allStarting = moduleContext.pkgRepo + .map( + _.getLoadedModules.filter(m => + exportedModuleRef.getName != m.getName && m + .getName() + .toString + .startsWith(exportedModuleRef.getName.toString + ".") + ) ) - ) - .getOrElse(Nil) - if (allStarting.nonEmpty) { - ir.exports.foreach { export => - export match { - case m: Export.Module - if m.name.name == resolution.qualifiedName.toString => - m.addDiagnostic( - warnings.Shadowed.TypeInModuleNameConflicts( - exportedModule.getName.toString, - tpeName, - allStarting.head.getName.toString, - m, - m.location + .getOrElse(Nil) + if (allStarting.nonEmpty) { + ir.exports.foreach { export => + export match { + case m: Export.Module + if m.name.name == resolution.qualifiedName.toString => + m.addDiagnostic( + warnings.Shadowed.TypeInModuleNameConflicts( + exportedModule.getName.toString, + tpeName, + allStarting.head.getName.toString, + m, + m.location + ) ) - ) - case _ => + case _ => + } } } } - } - case _ => + case _ => + } } } ir.copy(bindings = new_bindings) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala index 1d052ae2fb..a3932250fe 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala @@ -229,7 +229,7 @@ case object MethodDefinitions extends IRPass { "a method definition target" ) ) - case _: BindingsMap.ResolvedStaticMethod => + case _: BindingsMap.ResolvedExtensionMethod => errors.Resolution( typePointer, errors.Resolution.UnexpectedMethod( diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala index 8107959abb..27320954a2 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala @@ -194,7 +194,7 @@ object Patterns extends IRPass { ) ) r.setLocation(consName.location) - case Right(_: BindingsMap.ResolvedStaticMethod) => + case Right(_: BindingsMap.ResolvedExtensionMethod) => val r = errors.Resolution( consName, errors.Resolution.UnexpectedMethod( @@ -228,7 +228,7 @@ object Patterns extends IRPass { throw new CompilerError( "Impossible, should be transformed into an error before." ) - case BindingsMap.ResolvedStaticMethod(_, _) => + case BindingsMap.ResolvedExtensionMethod(_, _) => throw new CompilerError( "Impossible, should be transformed into an error before." ) @@ -303,7 +303,7 @@ object Patterns extends IRPass { errors.Resolution .UnexpectedMethod(s"method type pattern case") ) - case Right(_: BindingsMap.ResolvedStaticMethod) => + case Right(_: BindingsMap.ResolvedExtensionMethod) => errors.Resolution( tpeName, errors.Resolution.UnexpectedMethod( diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/BuiltinsIrBuilder.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/BuiltinsIrBuilder.scala index 0778a44a56..255aa98a2f 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/BuiltinsIrBuilder.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/BuiltinsIrBuilder.scala @@ -9,6 +9,8 @@ import org.enso.compiler.context.{ } import org.enso.compiler.data.CompilerConfig import org.enso.common.CompilationStage +import org.enso.compiler.phase.exports.ExportsResolution + import scala.util.Using /** A phase responsible for initializing the builtins' IR from the provided diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/ImportResolver.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/ImportResolver.scala index efa96e61dd..596bdf9869 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/ImportResolver.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/ImportResolver.scala @@ -98,9 +98,12 @@ final class ImportResolver(compiler: Compiler) extends ImportResolverForIR { } ) } - currentLocal.resolvedImports - .map(_.target.module.unsafeAsModule()) - .distinct + currentLocal.resolvedImports.flatMap { resolvedImport => + val targetModules = resolvedImport.targets.map { target => + target.module.unsafeAsModule() + } + targetModules + }.distinct } @scala.annotation.tailrec @@ -129,17 +132,18 @@ final class ImportResolver(compiler: Compiler) extends ImportResolverForIR { u.loadedFromCache(true) } ) - val converted = Option(current.getBindingsMap()) - ( - converted - .map( - _.resolvedImports - .map(_.target.module.unsafeAsModule()) - .distinct - ) - .getOrElse(Nil), - false - ) + val bm = current.getBindingsMap + if (bm != null) { + val modulesFromResolvedImps = bm.resolvedImports.flatMap { + resolvedImp => + resolvedImp.targets.map { target => + target.module.unsafeAsModule() + } + } + (modulesFromResolvedImps.distinct, false) + } else { + (Nil, false) + } case None => compiler.ensureParsed(current) (analyzeModule(current), true) @@ -180,9 +184,7 @@ final class ImportResolver(compiler: Compiler) extends ImportResolverForIR { case Export.Module( expName, rename, - isAll, onlyNames, - hiddenNames, _, isSynthetic, _, @@ -194,9 +196,9 @@ final class ImportResolver(compiler: Compiler) extends ImportResolverForIR { val syntheticImport = Import.Module( expName, rename, - isAll, + false, onlyNames, - hiddenNames, + None, location = None, isSynthetic = true, passData = new MetadataStorage(), diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/exports/Edge.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/exports/Edge.scala new file mode 100644 index 0000000000..e37a94ae3f --- /dev/null +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/exports/Edge.scala @@ -0,0 +1,8 @@ +package org.enso.compiler.phase.exports + +case class Edge( + exporter: Node, + symbols: List[String], + exportsAs: Option[String], + exportee: Node +) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/ExportsResolution.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/exports/ExportsResolution.scala similarity index 59% rename from engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/ExportsResolution.scala rename to engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/exports/ExportsResolution.scala index 78344651aa..6b28d13576 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/ExportsResolution.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/exports/ExportsResolution.scala @@ -1,17 +1,22 @@ -package org.enso.compiler.phase +package org.enso.compiler.phase.exports import org.enso.compiler.data.BindingsMap import org.enso.compiler.data.BindingsMap.ModuleReference.Concrete import org.enso.compiler.data.BindingsMap.{ ExportedModule, ImportTarget, + ResolvedConversionMethod, + ResolvedExtensionMethod, + ResolvedImport, ResolvedModule, - SymbolRestriction + ResolvedModuleMethod, + ResolvedName } import org.enso.compiler.context.CompilerContext import org.enso.compiler.context.CompilerContext.Module import scala.collection.mutable +import scala.jdk.CollectionConverters._ /** An exception signaling a loop in the export statements. * @param modules the modules forming the cycle. @@ -23,23 +28,9 @@ case class ExportCycleException(modules: List[Module]) class ExportsResolution(private val context: CompilerContext) { - private case class Edge( - exporter: Node, - symbols: SymbolRestriction, - exportsAs: Option[String], - exportee: Node - ) - - private case class Node( - module: ImportTarget - ) { - var exports: List[Edge] = List() - var exportedBy: List[Edge] = List() - } - private def getBindings(module: Module): BindingsMap = module.getBindingsMap() - private def buildGraph(modules: List[Module]): List[Node] = { + def buildModuleGraph(modules: List[Module]): List[Node] = { val moduleTargets = modules.map(m => ResolvedModule(Concrete(m))) val nodes = mutable.Map[ImportTarget, Node]( moduleTargets.map(mod => (mod, Node(mod))): _* @@ -61,9 +52,21 @@ class ExportsResolution(private val context: CompilerContext) { Nil } val node = nodes(module) - node.exports = exports.map { - case ExportedModule(mod, rename, restriction) => - Edge(node, restriction, rename, nodes.getOrElseUpdate(mod, Node(mod))) + node.exports = exports.flatMap { + case ExportedModule(exportedMod, rename, symbols) => + // Don't create edges for self-exports + if (exportedMod.qualifiedName != module.qualifiedName) { + Some( + Edge( + node, + symbols, + rename, + nodes.getOrElseUpdate(exportedMod, Node(exportedMod)) + ) + ) + } else { + None + } } node.exports.foreach { edge => edge.exportee.exportedBy ::= edge } } @@ -127,88 +130,84 @@ class ExportsResolution(private val context: CompilerContext) { result.reverse } - private def resolveExports(nodes: List[Node]): Unit = { - val exports = mutable.Map[ImportTarget, List[ExportedModule]]() - nodes.foreach { node => - val explicitlyExported = - node.exports.map(edge => - ExportedModule( - edge.exportee.module, - edge.exportsAs, - edge.symbols - ) - ) - val transitivelyExported: List[ExportedModule] = - explicitlyExported.flatMap { - case ExportedModule(module, _, restriction) => - exports(module).map { - case ExportedModule(export, _, parentRestriction) => - ExportedModule( - export, - None, - SymbolRestriction.Intersect( - List(restriction, parentRestriction) - ) - ) - } - } - val allExported = explicitlyExported ++ transitivelyExported - val unified = allExported - .groupBy(_.target) - .map { case (mod, items) => - val name = items.collectFirst { case ExportedModule(_, Some(n), _) => - n - } - val itemsUnion = SymbolRestriction.Union(items.map(_.symbols)) - ExportedModule(mod, name, itemsUnion) - } - .toList - exports(node.module) = unified - } - exports.foreach { case (module, exports) => - module match { - case _: BindingsMap.ResolvedType => - case ResolvedModule(module) => - getBindings(module.unsafeAsModule()).resolvedExports = - exports.map(ex => ex.copy(symbols = ex.symbols.optimize)) - } - } - } - + /** Fills in the [[BindingsMap.exportedSymbols]] field for every given module. + * This field is only filled with the symbols that are resolved. The symbols + * that are not resolved are ignored at this staged. + */ private def resolveExportedSymbols(modules: List[Module]): Unit = { modules.foreach { module => val bindings = getBindings(module) val ownEntities = bindings.definedEntities .filter(_.canExport) - .map(e => (e.name, List(e.resolvedIn(module)))) - val exportedModules = bindings.resolvedExports.collect { - case ExportedModule(mod, Some(name), _) - if mod.module.unsafeAsModule() != module => - (name, List(mod)) - } - val reExportedSymbols = bindings.resolvedExports.flatMap { export => - export.target.exportedSymbols - .flatMap { case (sym, resolutions) => - val allowedResolutions = - resolutions.filter(res => export.symbols.canAccess(sym, res)) - if (allowedResolutions.isEmpty) None - else Some((sym, allowedResolutions)) + .map(e => (e.name, e.resolvedIn(module))) + val expSymbolsFromResolvedImps: List[(String, ResolvedName)] = + bindings.resolvedImports + .collect { case ResolvedImport(_, exports, targets) => + exports.flatMap { export => + targets.flatMap { target => + val symbols = export.onlyNames match { + case Some(onlyNames) => + onlyNames.map(_.name) + case None => + List(export.name.parts.last.name) + } + symbols.flatMap { symbol => + export.rename match { + case Some(rename) => + Some((rename.name, target)) + case None => + Some((symbol, target)) + } + } + } + } } - } + .flatten + .distinct + bindings.exportedSymbols = List( ownEntities, - exportedModules, - reExportedSymbols - ).flatten.groupBy(_._1).map { case (m, names) => - (m, names.flatMap(_._2).distinct) + expSymbolsFromResolvedImps + ).flatten.distinct + .groupBy(_._1) + .map { case (symbolName, duplicateResolutions) => + val resolvedNames = duplicateResolutions.map(_._2) + if (!areResolvedNamesConsistent(resolvedNames)) { + throw ConflictingResolutionsError + .create(module.getName, resolvedNames.asJava) + } + (symbolName, resolvedNames) + } + } + } + + /** If there are multiple resolved names for one exported symbol, they must be consistent. + * I.e., either they are all static (extension) and module methods, or all conversion methods. + * We cannot, for example, export type and a module for one symbol - that would result + * in a collision. + * @return true if they are consistent, false otherwise. + */ + private def areResolvedNamesConsistent( + resolvedNames: List[ResolvedName] + ): Boolean = { + if (resolvedNames.size > 1) { + val allStaticOrModuleMethods = resolvedNames.forall { + case _: ResolvedExtensionMethod => true + case _: ResolvedModuleMethod => true + case _ => false } + val allConversionMethods = + resolvedNames.forall(_.isInstanceOf[ResolvedConversionMethod]) + allStaticOrModuleMethods || allConversionMethods + } else { + true } } /** Performs exports resolution on a selected set of modules. * - * The exports graph is validated and stored in the individual modules, + * The exports graph is validated and stored in the individual modules' binding maps, * allowing further use. * * The method returns a list containing the original modules, in @@ -221,18 +220,17 @@ class ExportsResolution(private val context: CompilerContext) { */ @throws[ExportCycleException] def run(modules: List[Module]): List[Module] = { - val graph = buildGraph(modules) + val graph = buildModuleGraph(modules) val cycles = findCycles(graph) if (cycles.nonEmpty) { throw ExportCycleException( cycles.head.map(_.module.module.unsafeAsModule()) ) } - val tops = topsort(graph) - resolveExports(tops) + val tops = topsort(graph) val topModules = tops.map(_.module) - resolveExportedSymbols(tops.map(_.module).collect { - case m: ResolvedModule => m.module.unsafeAsModule() + resolveExportedSymbols(topModules.collect { case m: ResolvedModule => + m.module.unsafeAsModule() }) // Take _last_ occurrence of each module topModules.map(_.module.unsafeAsModule()).reverse.distinct.reverse @@ -242,7 +240,7 @@ class ExportsResolution(private val context: CompilerContext) { * neither performs cycle checks nor resolves exports. */ def runSort(modules: List[Module]): List[Module] = { - val graph = buildGraph(modules) + val graph = buildModuleGraph(modules) val tops = topsort(graph) val topModules = tops.map(_.module) topModules.map(_.module.unsafeAsModule()).reverse.distinct.reverse diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/exports/Node.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/exports/Node.scala new file mode 100644 index 0000000000..9519beadc4 --- /dev/null +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/phase/exports/Node.scala @@ -0,0 +1,10 @@ +package org.enso.compiler.phase.exports + +import org.enso.compiler.data.BindingsMap.ResolvedModule + +case class Node( + module: ResolvedModule +) { + var exports: List[Edge] = List() + var exportedBy: List[Edge] = List() +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ErrorCompilerTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ErrorCompilerTest.java index ef1c05b488..c3dacedf56 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ErrorCompilerTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ErrorCompilerTest.java @@ -408,19 +408,22 @@ public class ErrorCompilerTest extends CompilerTest { @Test public void malformedExport9() throws Exception { var ir = parse("from export all"); - assertSingleSyntaxError(ir, invalidExport("Expected tokens."), null, 4, 4); + assertSingleSyntaxError( + ir, invalidExport("`all` not allowed in `export` statement"), null, 0, 15); } @Test public void malformedExport10() throws Exception { var ir = parse("from Foo export all hiding"); - assertSingleSyntaxError(ir, invalidExport("Expected tokens."), null, 26, 26); + assertSingleSyntaxError( + ir, invalidExport("`hiding` not allowed in `export` statement"), null, 0, 26); } @Test public void malformedExport11() throws Exception { var ir = parse("from Foo export all hiding X.Y"); - assertSingleSyntaxError(ir, invalidExport("Expected identifier."), null, 27, 30); + assertSingleSyntaxError( + ir, invalidExport("`hiding` not allowed in `export` statement"), null, 0, 30); } @Test @@ -593,6 +596,24 @@ public class ErrorCompilerTest extends CompilerTest { assertTrue(method.body() instanceof Empty); } + @Test + public void exportAllIsNotAllowed() { + var ir = parse(""" + from project.Module export all + """); + var expectedReason = new Syntax.InvalidExport("`all` not allowed in `export` statement"); + assertSingleSyntaxError(ir, expectedReason, null, 0, 30); + } + + @Test + public void exportHidingIsNotAllowed() { + var ir = parse(""" + from project.Module export all hiding Foo + """); + var expectedReason = new Syntax.InvalidExport("`hiding` not allowed in `export` statement"); + assertSingleSyntaxError(ir, expectedReason, null, 0, 41); + } + private void assertSingleSyntaxError( Module ir, Syntax.Reason type, String msg, int start, int end) { var errors = assertIR(ir, Syntax.class, 1); diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ExportCycleDetectionTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ExportCycleDetectionTest.java new file mode 100644 index 0000000000..91542b5515 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ExportCycleDetectionTest.java @@ -0,0 +1,179 @@ +package org.enso.compiler; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Set; +import org.enso.pkg.QualifiedName; +import org.enso.polyglot.PolyglotContext; +import org.enso.polyglot.RuntimeOptions; +import org.enso.test.utils.ContextUtils; +import org.enso.test.utils.ModuleUtils; +import org.enso.test.utils.ProjectUtils; +import org.enso.test.utils.SourceModule; +import org.graalvm.polyglot.PolyglotException; +import org.hamcrest.Matcher; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class ExportCycleDetectionTest { + @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void detectCycleInTwoModules() throws IOException { + var aMod = + new SourceModule( + QualifiedName.fromString("A_Module"), + """ + from project.B_Module export B_Type + type A_Type + """); + var bMod = + new SourceModule( + QualifiedName.fromString("B_Module"), + """ + from project.A_Module export A_Type + type B_Type + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + import project.A_Module + import project.B_Module + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(aMod, bMod, mainMod), projDir); + expectProjectCompilationError( + projDir, + allOf( + containsString("Export statements form a cycle"), + containsString("local.Proj.A_Module"), + containsString("local.Proj.B_Module"))); + } + + @Test + public void detectCycleInThreeModules() throws IOException { + var aMod = + new SourceModule( + QualifiedName.fromString("A_Module"), + """ + from project.B_Module export C_Type + type A_Type + """); + var bMod = + new SourceModule( + QualifiedName.fromString("B_Module"), + """ + from project.C_Module export C_Type + """); + var cMod = + new SourceModule( + QualifiedName.fromString("C_Module"), + """ + from project.A_Module export A_Type + type C_Type + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + import project.A_Module + import project.B_Module + import project.C_Module + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(aMod, bMod, cMod, mainMod), projDir); + // The reported ordering of cycle error is non-deterministic. We don't care about it. + // Just check that all modules are involved in the cycle error report. + expectProjectCompilationError( + projDir, + allOf( + containsString("Export statements form a cycle"), + containsString("local.Proj.A_Module"), + containsString("local.Proj.B_Module"), + containsString("local.Proj.C_Module"))); + } + + @Test + public void noCycleDetectedWhenExportingSymbolsFromItself_1() throws IOException { + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + type Main_Type + export project.Main.Main_Type + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(mainMod), projDir); + try (var ctx = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .build()) { + var polyCtx = new PolyglotContext(ctx); + try { + polyCtx.getTopScope().compile(true); + } catch (PolyglotException e) { + fail("Compilation error not expected. But got: " + e); + } + var exportedSyms = ModuleUtils.getExportedSymbolsFromModule(ctx, "local.Proj.Main"); + assertThat(exportedSyms.size(), is(1)); + assertThat(exportedSyms, hasKey("Main_Type")); + } + } + + @Test + public void noCycleDetectedWhenExportingSymbolsFromItself_2() throws IOException { + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + type Main_Type + from project.Main export Main_Type + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(mainMod), projDir); + try (var ctx = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .build()) { + var polyCtx = new PolyglotContext(ctx); + try { + polyCtx.getTopScope().compile(true); + } catch (PolyglotException e) { + fail("Compilation error not expected. But got: " + e); + } + var exportedSyms = ModuleUtils.getExportedSymbolsFromModule(ctx, "local.Proj.Main"); + assertThat(exportedSyms.size(), is(1)); + assertThat(exportedSyms, hasKey("Main_Type")); + } + } + + private void expectProjectCompilationError(Path projDir, Matcher errMsgMatcher) { + var out = new ByteArrayOutputStream(); + try (var ctx = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .option(RuntimeOptions.STRICT_ERRORS, "true") + .out(out) + .err(out) + .build()) { + var polyCtx = new PolyglotContext(ctx); + try { + polyCtx.getTopScope().compile(true); + fail("Expected compilation error"); + } catch (PolyglotException e) { + assertThat(e.getMessage(), containsString("Compilation aborted")); + } + } + assertThat(out.toString(), errMsgMatcher); + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ExportedSymbolsTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ExportedSymbolsTest.java new file mode 100644 index 0000000000..7a7c22259c --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ExportedSymbolsTest.java @@ -0,0 +1,248 @@ +package org.enso.compiler; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.enso.compiler.context.CompilerContext.Module; +import org.enso.compiler.data.BindingsMap; +import org.enso.pkg.QualifiedName; +import org.enso.polyglot.PolyglotContext; +import org.enso.polyglot.RuntimeOptions; +import org.enso.test.utils.ContextUtils; +import org.enso.test.utils.ProjectUtils; +import org.enso.test.utils.SourceModule; +import org.graalvm.polyglot.Context; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import scala.jdk.javaapi.CollectionConverters; + +public class ExportedSymbolsTest { + private Path projDir; + + @Before + public void setup() throws IOException { + this.projDir = Files.createTempDirectory("exported-symbols-test"); + } + + @After + public void tearDown() throws IOException { + ProjectUtils.deleteRecursively(projDir); + } + + @Test + public void exportedSymbolsFromSingleModule() throws IOException { + var mainSrcMod = + new SourceModule(QualifiedName.fromString("Main"), """ + type A_Type + """); + ProjectUtils.createProject("Proj", Set.of(mainSrcMod), projDir); + var ctx = createCtx(projDir); + compile(ctx); + var mainExportedSymbols = getExportedSymbolsFromModule(ctx, "local.Proj.Main"); + assertThat(mainExportedSymbols.size(), is(1)); + assertThat(mainExportedSymbols.containsKey("A_Type"), is(true)); + assertThat( + mainExportedSymbols.get("A_Type").get(0), instanceOf(BindingsMap.ResolvedType.class)); + } + + @Test + public void transitivelyExportedSymbols() throws IOException { + var aMod = + new SourceModule(QualifiedName.fromString("A_Module"), """ + type A_Type + """); + var mainSrcMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + export project.A_Module.A_Type + type B_Type + """); + ProjectUtils.createProject("Proj", Set.of(aMod, mainSrcMod), projDir); + var ctx = createCtx(projDir); + compile(ctx); + var mainExportedSymbols = getExportedSymbolsFromModule(ctx, "local.Proj.Main"); + assertThat(mainExportedSymbols.size(), is(2)); + assertThat(mainExportedSymbols.keySet(), containsInAnyOrder("A_Type", "B_Type")); + } + + @Test + public void exportSymbolFromDifferentModule() throws IOException { + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + export project.B_Module.B_Type + type A_Type + """); + var bMod = + new SourceModule(QualifiedName.fromString("B_Module"), """ + type B_Type + """); + ProjectUtils.createProject("Proj", Set.of(mainMod, bMod), projDir); + var ctx = createCtx(projDir); + compile(ctx); + var mainExportedSymbols = getExportedSymbolsFromModule(ctx, "local.Proj.Main"); + assertThat(mainExportedSymbols.size(), is(2)); + assertThat(mainExportedSymbols.keySet(), containsInAnyOrder("A_Type", "B_Type")); + } + + @Test + public void exportRenamedSymbol() throws IOException { + var aMod = + new SourceModule(QualifiedName.fromString("A_Module"), """ + type A_Type + """); + var mainSrcMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + export project.A_Module.A_Type as Foo + """); + ProjectUtils.createProject("Proj", Set.of(aMod, mainSrcMod), projDir); + var ctx = createCtx(projDir); + compile(ctx); + var mainExportedSymbols = getExportedSymbolsFromModule(ctx, "local.Proj.Main"); + assertThat(mainExportedSymbols.size(), is(1)); + assertThat(mainExportedSymbols.keySet(), containsInAnyOrder("Foo")); + } + + @Test + public void exportedSymbolsFromSubModule() throws IOException { + var aMod = + new SourceModule( + QualifiedName.fromString("Synthetic_Module.A_Module"), + """ + type A_Module + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + import project.Synthetic_Module + """); + ProjectUtils.createProject("Proj", Set.of(aMod, mainMod), projDir); + var ctx = createCtx(projDir); + compile(ctx); + var syntheticModExpSymbols = getExportedSymbolsFromModule(ctx, "local.Proj.Synthetic_Module"); + assertThat( + "Just a A_Module submodule should be exported", syntheticModExpSymbols.size(), is(1)); + assertThat( + "Just a A_Module submodule should be exported", syntheticModExpSymbols, hasKey("A_Module")); + } + + @Test + public void exportTypeFromModuleWithSameName() throws IOException { + var aMod = + new SourceModule( + QualifiedName.fromString("A_Module"), """ + type A_Module + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + export project.A_Module.A_Module + """); + ProjectUtils.createProject("Proj", Set.of(aMod, mainMod), projDir); + var ctx = createCtx(projDir); + compile(ctx); + var mainExportedSymbols = getExportedSymbolsFromModule(ctx, "local.Proj.Main"); + assertThat(mainExportedSymbols.size(), is(1)); + assertThat(mainExportedSymbols.keySet(), containsInAnyOrder("A_Module")); + assertThat(mainExportedSymbols.get("A_Module").size(), is(1)); + assertThat( + mainExportedSymbols.get("A_Module").get(0), is(instanceOf(BindingsMap.ResolvedType.class))); + } + + @Test + public void exportModuleWithTypeWithSameName() throws IOException { + var aMod = + new SourceModule( + QualifiedName.fromString("A_Module"), """ + type A_Module + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), """ + export project.A_Module + """); + ProjectUtils.createProject("Proj", Set.of(aMod, mainMod), projDir); + var ctx = createCtx(projDir); + compile(ctx); + var mainExportedSymbols = getExportedSymbolsFromModule(ctx, "local.Proj.Main"); + assertThat(mainExportedSymbols.size(), is(1)); + assertThat(mainExportedSymbols.keySet(), containsInAnyOrder("A_Module")); + assertThat(mainExportedSymbols.get("A_Module").size(), is(1)); + assertThat( + mainExportedSymbols.get("A_Module").get(0), + is(instanceOf(BindingsMap.ResolvedModule.class))); + } + + @Test + public void exportSyntheticModule() throws IOException { + var aMod = + new SourceModule( + QualifiedName.fromString("Synthetic_Module.A_Module"), + """ + type A_Type + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + export project.Synthetic_Module + """); + ProjectUtils.createProject("Proj", Set.of(aMod, mainMod), projDir); + var ctx = createCtx(projDir); + compile(ctx); + var mainExportedSymbols = getExportedSymbolsFromModule(ctx, "local.Proj.Main"); + assertThat(mainExportedSymbols.size(), is(1)); + assertThat(mainExportedSymbols.keySet(), containsInAnyOrder("Synthetic_Module")); + assertThat(mainExportedSymbols.get("Synthetic_Module").size(), is(1)); + assertThat( + mainExportedSymbols.get("Synthetic_Module").get(0), + is(instanceOf(BindingsMap.ResolvedModule.class))); + } + + private static Context createCtx(Path projDir) { + return ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .build(); + } + + private static void compile(Context ctx) { + new PolyglotContext(ctx).getTopScope().compile(true); + } + + private static Map> getExportedSymbolsFromModule( + Context ctx, String modName) { + var ensoCtx = ContextUtils.leakContext(ctx); + var mod = ensoCtx.getPackageRepository().getLoadedModule(modName).get(); + return getExportedSymbols(mod); + } + + private static Map> getExportedSymbols(Module module) { + var bindings = new HashMap>(); + var bindingsScala = module.getBindingsMap().exportedSymbols(); + bindingsScala.foreach( + entry -> { + var symbol = entry._1; + var resolvedNames = CollectionConverters.asJava(entry._2.toSeq()); + bindings.put(symbol, resolvedNames); + return null; + }); + return bindings; + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ImportsAndFQNConsistencyTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ImportsAndFQNConsistencyTest.java index 0f35f9be46..dbd08eb1b4 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ImportsAndFQNConsistencyTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/ImportsAndFQNConsistencyTest.java @@ -11,8 +11,10 @@ import java.util.List; import java.util.stream.Collectors; import org.enso.common.LanguageInfo; import org.enso.compiler.core.ir.module.scope.Definition; -import org.enso.compiler.core.ir.module.scope.Export; import org.enso.compiler.core.ir.module.scope.definition.Method; +import org.enso.compiler.data.BindingsMap.ResolvedConstructor; +import org.enso.compiler.data.BindingsMap.ResolvedModule; +import org.enso.compiler.data.BindingsMap.ResolvedType; import org.enso.interpreter.runtime.EnsoContext; import org.enso.polyglot.RuntimeOptions; import org.enso.test.utils.ContextUtils; @@ -27,6 +29,7 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.junit.runners.model.Statement; +import scala.jdk.javaapi.CollectionConverters; /** * If a symbol is importable by an import statement, then it should also be accessible via FQN @@ -218,16 +221,21 @@ main = 42 private static List gatherExportedSymbols(String moduleName, EnsoContext ensoCtx) { var mod = ensoCtx.getPackageRepository().getLoadedModule(moduleName); assertThat(mod.isDefined(), is(true)); - var exports = mod.get().getIr().exports(); - assertThat(exports.size(), greaterThan(1)); + var bmExpSymbols = CollectionConverters.asJava(mod.get().getBindingsMap().exportedSymbols()); List exportedSymbols = new ArrayList<>(); - exports.foreach( - export -> { - if (export instanceof Export.Module moduleExport) { - exportedSymbols.add(moduleExport.name().name()); - } - return null; - }); + for (var entry : bmExpSymbols.entrySet()) { + var resolvedNames = entry.getValue(); + // We are not interested in exported symbols that resolve to multiple targets. + // We are interested only in ResolvedModule, ResolvedType, and ResolvedConstructor. + if (resolvedNames.size() == 1) { + var exportSymTarget = resolvedNames.apply(0); + if (exportSymTarget instanceof ResolvedType + || exportSymTarget instanceof ResolvedModule + || exportSymTarget instanceof ResolvedConstructor) { + exportedSymbols.add(exportSymTarget.qualifiedName().toString()); + } + } + } return exportedSymbols; } diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportConstructorTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportConstructorTest.java new file mode 100644 index 0000000000..558b9f056d --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportConstructorTest.java @@ -0,0 +1,91 @@ +package org.enso.interpreter.test.exports; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; + +import java.io.IOException; +import java.util.Set; +import org.enso.pkg.QualifiedName; +import org.enso.polyglot.PolyglotContext; +import org.enso.polyglot.RuntimeOptions; +import org.enso.test.utils.ContextUtils; +import org.enso.test.utils.ModuleUtils; +import org.enso.test.utils.ProjectUtils; +import org.enso.test.utils.SourceModule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class ExportConstructorTest { + @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void exportedConstructorsAreInBindingMap() throws IOException { + var booleanMod = + new SourceModule( + QualifiedName.fromString("Boolean"), + """ + type Boolean + True + False + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + export project.Boolean.Boolean.True + export project.Boolean.Boolean.False + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(booleanMod, mainMod), projDir); + + try (var ctx = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .build()) { + var polyCtx = new PolyglotContext(ctx); + polyCtx.getTopScope().compile(true); + var mainModExportedSymbols = ModuleUtils.getExportedSymbolsFromModule(ctx, "local.Proj.Main"); + assertThat(mainModExportedSymbols.size(), is(2)); + assertThat(mainModExportedSymbols, allOf(hasKey("True"), hasKey("False"))); + } + } + + @Test + public void constructorsCanBeExportedFromTheSameModule() throws IOException { + var booleanMod = + new SourceModule( + QualifiedName.fromString("Boolean"), + """ + export project.Boolean.Boolean.True + export project.Boolean.Boolean.False + type Boolean + True + False + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + # Import just the module on purpose + import project.Boolean + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(booleanMod, mainMod), projDir); + + try (var ctx = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .build()) { + var polyCtx = new PolyglotContext(ctx); + polyCtx.getTopScope().compile(true); + var boolModExportedSymbols = + ModuleUtils.getExportedSymbolsFromModule(ctx, "local.Proj.Boolean"); + assertThat(boolModExportedSymbols.size(), is(3)); + assertThat(boolModExportedSymbols.keySet(), containsInAnyOrder("True", "False", "Boolean")); + } + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportConversionMethodTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportConversionMethodTest.java new file mode 100644 index 0000000000..c0dabd1bc2 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportConversionMethodTest.java @@ -0,0 +1,155 @@ +package org.enso.interpreter.test.exports; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +import java.io.IOException; +import java.util.Set; +import org.enso.compiler.data.BindingsMap.ResolvedConversionMethod; +import org.enso.pkg.QualifiedName; +import org.enso.polyglot.PolyglotContext; +import org.enso.polyglot.RuntimeOptions; +import org.enso.test.utils.ContextUtils; +import org.enso.test.utils.ModuleUtils; +import org.enso.test.utils.ProjectUtils; +import org.enso.test.utils.SourceModule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class ExportConversionMethodTest { + + @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void conversionMethodCanBeImportedByName() throws IOException { + var aMod = + new SourceModule( + QualifiedName.fromString("A_Module"), + """ + type A_Type + Value + type B_Type + """); + var bMod = + new SourceModule( + QualifiedName.fromString("B_Module"), + """ + import project.A_Module.A_Type + import project.A_Module.B_Type + + B_Type.from (_:A_Type) = 42 + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), """ + import project.B_Module.from + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(aMod, bMod, mainMod), projDir); + try (var ctx = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .build()) { + var polyCtx = new PolyglotContext(ctx); + polyCtx.getTopScope().compile(true); + + var mainResolvedImps = ModuleUtils.getResolvedImports(ctx, "local.Proj.Main"); + assertThat(mainResolvedImps.size(), is(1)); + assertThat(mainResolvedImps.get(0).targets().size(), is(1)); + assertThat( + mainResolvedImps.get(0).targets().head(), is(instanceOf(ResolvedConversionMethod.class))); + } + } + + @Test + public void conversionMethodIsInBindingMap() throws IOException { + var aMod = + new SourceModule( + QualifiedName.fromString("A_Module"), + """ + type A_Type + type B_Type + B_Type.from (_:A_Type) = 42 + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), """ + export project.A_Module.from + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(aMod, mainMod), projDir); + + try (var ctx = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .build()) { + var polyCtx = new PolyglotContext(ctx); + polyCtx.getTopScope().compile(true); + + var aModExportedSymbols = + ModuleUtils.getExportedSymbolsFromModule(ctx, "local.Proj.A_Module"); + assertThat(aModExportedSymbols.size(), is(3)); + assertThat(aModExportedSymbols.keySet(), containsInAnyOrder("A_Type", "B_Type", "from")); + + var mainResolvedImps = ModuleUtils.getResolvedImports(ctx, "local.Proj.Main"); + assertThat(mainResolvedImps.size(), is(1)); + assertThat(mainResolvedImps.get(0).targets().size(), is(1)); + assertThat( + mainResolvedImps.get(0).targets().head(), is(instanceOf(ResolvedConversionMethod.class))); + var mainExportedSyms = ModuleUtils.getExportedSymbolsFromModule(ctx, "local.Proj.Main"); + assertThat(mainExportedSyms.size(), is(1)); + assertThat(mainExportedSyms, hasKey("from")); + } + } + + @Test + public void multipleConversionMethodsCanBeImported() throws IOException { + var aMod = + new SourceModule( + QualifiedName.fromString("A_Module"), + """ + type A_Type + type B_Type + B_Type.from (_:A_Type) = 1 + A_Type.from (_:B_Type) = 2 + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), """ + export project.A_Module.from + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(aMod, mainMod), projDir); + + try (var ctx = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .build()) { + var polyCtx = new PolyglotContext(ctx); + polyCtx.getTopScope().compile(true); + + var aModExportedSymbols = + ModuleUtils.getExportedSymbolsFromModule(ctx, "local.Proj.A_Module"); + assertThat(aModExportedSymbols.size(), is(3)); + assertThat(aModExportedSymbols.keySet(), containsInAnyOrder("A_Type", "B_Type", "from")); + + var mainResolvedImps = ModuleUtils.getResolvedImports(ctx, "local.Proj.Main"); + assertThat(mainResolvedImps.size(), is(1)); + assertThat(mainResolvedImps.get(0).targets().size(), is(2)); + assertThat( + mainResolvedImps.get(0).targets().apply(0), + is(instanceOf(ResolvedConversionMethod.class))); + assertThat( + mainResolvedImps.get(0).targets().apply(1), + is(instanceOf(ResolvedConversionMethod.class))); + var mainExportedSyms = ModuleUtils.getExportedSymbolsFromModule(ctx, "local.Proj.Main"); + assertThat(mainExportedSyms.size(), is(1)); + assertThat(mainExportedSyms, hasKey("from")); + assertThat(mainExportedSyms.get("from").size(), is(2)); + } + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportExtensionMethodTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportExtensionMethodTest.java index f795d0f405..396e44a1f7 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportExtensionMethodTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportExtensionMethodTest.java @@ -1,8 +1,8 @@ package org.enso.interpreter.test.exports; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.is; import java.io.IOException; @@ -36,7 +36,8 @@ public class ExportExtensionMethodTest { new SourceModule( QualifiedName.fromString("A_Module"), """ - from project.T_Module export My_Type, extension_method + export project.T_Module.My_Type + export project.T_Module.extension_method """); var mainMod = new SourceModule( @@ -74,7 +75,9 @@ public class ExportExtensionMethodTest { new SourceModule( QualifiedName.fromString("A_Module"), """ - from project.T_Module export My_Type, My_Other_Type, extension_method + export project.T_Module.My_Type + export project.T_Module.My_Other_Type + export project.T_Module.extension_method """); var mainMod = new SourceModule( @@ -110,7 +113,8 @@ public class ExportExtensionMethodTest { new SourceModule( QualifiedName.fromString("Main"), """ - from project.T_Module export My_Type, extension_method + export project.T_Module.My_Type + export project.T_Module.extension_method """); var projDir = tempFolder.newFolder().toPath(); ProjectUtils.createProject("Proj", Set.of(tMod, mainMod), projDir); @@ -121,7 +125,9 @@ public class ExportExtensionMethodTest { var polyCtx = new PolyglotContext(ctx); polyCtx.getTopScope().compile(true); var mainModExportedSymbols = ModuleUtils.getExportedSymbolsFromModule(ctx, "local.Proj.Main"); - assertThat(mainModExportedSymbols, hasKey("extension_method")); + assertThat(mainModExportedSymbols.size(), is(2)); + assertThat( + mainModExportedSymbols.keySet(), containsInAnyOrder("My_Type", "extension_method")); } } diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportModuleTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportModuleTest.java new file mode 100644 index 0000000000..217dfab4c2 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportModuleTest.java @@ -0,0 +1,92 @@ +package org.enso.interpreter.test.exports; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +import java.io.IOException; +import java.util.Set; +import org.enso.compiler.data.BindingsMap.ResolvedModule; +import org.enso.pkg.QualifiedName; +import org.enso.polyglot.PolyglotContext; +import org.enso.polyglot.RuntimeOptions; +import org.enso.test.utils.ContextUtils; +import org.enso.test.utils.ModuleUtils; +import org.enso.test.utils.ProjectUtils; +import org.enso.test.utils.SourceModule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class ExportModuleTest { + @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void exportSubModuleFromExistingModule() throws IOException { + var subModule = + new SourceModule( + QualifiedName.fromString("Module.SubModule"), + """ + # Blank on purpose + """); + var module = + new SourceModule( + QualifiedName.fromString("Module"), + """ + # Blank on purpose - ensures that `Module` is not just synthetic + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + export project.Module.SubModule + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(subModule, module, mainMod), projDir); + try (var ctx = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .build()) { + var polyCtx = new PolyglotContext(ctx); + polyCtx.getTopScope().compile(true); + var mainModExportedSymbols = ModuleUtils.getExportedSymbolsFromModule(ctx, "local.Proj.Main"); + assertThat(mainModExportedSymbols.size(), is(1)); + assertThat(mainModExportedSymbols, hasKey("SubModule")); + assertThat(mainModExportedSymbols.get("SubModule").size(), is(1)); + assertThat( + mainModExportedSymbols.get("SubModule").get(0), is(instanceOf(ResolvedModule.class))); + } + } + + @Test + public void exportSubModuleFromSyntheticModule() throws IOException { + var subModule = + new SourceModule( + QualifiedName.fromString("Module.SubModule"), + """ + # Blank on purpose + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + export project.Module.SubModule + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(subModule, mainMod), projDir); + try (var ctx = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .build()) { + var polyCtx = new PolyglotContext(ctx); + polyCtx.getTopScope().compile(true); + var mainModExportedSymbols = ModuleUtils.getExportedSymbolsFromModule(ctx, "local.Proj.Main"); + assertThat(mainModExportedSymbols.size(), is(1)); + assertThat(mainModExportedSymbols, hasKey("SubModule")); + assertThat(mainModExportedSymbols.get("SubModule").size(), is(1)); + assertThat( + mainModExportedSymbols.get("SubModule").get(0), is(instanceOf(ResolvedModule.class))); + } + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportResolutionOrderingTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportResolutionOrderingTest.java new file mode 100644 index 0000000000..d5d93dddc8 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportResolutionOrderingTest.java @@ -0,0 +1,174 @@ +package org.enso.interpreter.test.exports; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.contains; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; +import org.enso.compiler.phase.exports.ExportsResolution; +import org.enso.interpreter.runtime.Module; +import org.enso.pkg.QualifiedName; +import org.enso.polyglot.PolyglotContext; +import org.enso.polyglot.RuntimeOptions; +import org.enso.test.utils.ContextUtils; +import org.enso.test.utils.ModuleUtils; +import org.enso.test.utils.ProjectUtils; +import org.enso.test.utils.SourceModule; +import org.graalvm.polyglot.Context; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import scala.jdk.javaapi.CollectionConverters; + +/** + * Tests ordering of modules from {@link + * org.enso.compiler.phase.exports.ExportsResolution#runSort(scala.collection.immutable.List)}. Some + * tests are already in {@link org.enso.compiler.test.semantic.ImportExportTest}, but there are some + * limitations, like no ability to create (and test) ordering of synthetic modules. + */ +public class ExportResolutionOrderingTest { + @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void testOrderingWithSubmoduleOfSyntheticModule() throws IOException { + var aMod = + new SourceModule( + QualifiedName.fromString("Synthetic_Module.A_Module"), + """ + type A_Type + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + export project.Synthetic_Module.A_Module.A_Type + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(aMod, mainMod), projDir); + + try (var ctx = createContext(projDir)) { + compile(ctx); + var mainRuntimeMod = getLoadedModule(ctx, "local.Proj.Main"); + var aRuntimeMod = getLoadedModule(ctx, "local.Proj.Synthetic_Module.A_Module"); + var syntheticRuntimeMod = getLoadedModule(ctx, "local.Proj.Synthetic_Module"); + var sortedModules = + runExportsResolutionSort(List.of(mainRuntimeMod, aRuntimeMod, syntheticRuntimeMod), ctx); + assertThat( + "Export relations should be: mainMod --> syntheticMod --> aMod", + sortedModules, + contains(aRuntimeMod, syntheticRuntimeMod, mainRuntimeMod)); + } + } + + @Test + public void testOrderingWithTwoSubmodulesOfSyntheticModule() throws IOException { + var aMod = + new SourceModule( + QualifiedName.fromString("Synthetic_Module.A_Module"), + """ + type A_Type + """); + var bMod = + new SourceModule( + QualifiedName.fromString("Synthetic_Module.B_Module"), + """ + type B_Type + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + export project.Synthetic_Module.A_Module.A_Type + export project.Synthetic_Module.B_Module.B_Type + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(aMod, bMod, mainMod), projDir); + + try (var ctx = createContext(projDir)) { + compile(ctx); + var mainRuntimeMod = getLoadedModule(ctx, "local.Proj.Main"); + var aRuntimeMod = getLoadedModule(ctx, "local.Proj.Synthetic_Module.A_Module"); + var bRuntimeMod = getLoadedModule(ctx, "local.Proj.Synthetic_Module.B_Module"); + var syntheticRuntimeMod = getLoadedModule(ctx, "local.Proj.Synthetic_Module"); + var sortedModules = + runExportsResolutionSort( + List.of(mainRuntimeMod, aRuntimeMod, bRuntimeMod, syntheticRuntimeMod), ctx); + assertThat( + "Export relations should be: mainMod --> syntheticMod --> aMod; mainMod --> syntheticMod" + + " --> bMod", + sortedModules, + anyOf( + contains(bRuntimeMod, aRuntimeMod, syntheticRuntimeMod, mainRuntimeMod), + contains(aRuntimeMod, bRuntimeMod, syntheticRuntimeMod, mainRuntimeMod))); + } + } + + // TODO: Tracked by https://github.com/enso-org/enso/issues/10505 + @Ignore + @Test + public void testOrderingWithTwoSyntheticModules() throws IOException { + var aMod = + new SourceModule( + QualifiedName.fromString("Syn_1.Syn_2.A_Module"), """ + type A_Type + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + export project.Syn_1.Syn_2.A_Module.A_Type + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(aMod, mainMod), projDir); + + try (var ctx = createContext(projDir)) { + compile(ctx); + var mainRuntimeMod = getLoadedModule(ctx, "local.Proj.Main"); + var aRuntimeMod = getLoadedModule(ctx, "local.Proj.Syn_1.Syn_2.A_Module"); + var syn1RuntimeMod = getLoadedModule(ctx, "local.Proj.Syn_1"); + var syn2RuntimeMod = getLoadedModule(ctx, "local.Proj.Syn_1.Syn_2"); + var sortedModules = + runExportsResolutionSort( + List.of(mainRuntimeMod, aRuntimeMod, syn1RuntimeMod, syn2RuntimeMod), ctx); + var sortedModNames = sortedModules.stream().map(m -> m.getName().toString()).toList(); + assertThat( + "Export relations should be: mainMod --> syn1Mod --> syn2Mod --> aMod, but was: " + + sortedModNames, + sortedModules, + contains(aRuntimeMod, syn2RuntimeMod, syn1RuntimeMod, mainRuntimeMod)); + } + } + + private static Context createContext(Path projDir) { + return ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .build(); + } + + private static void compile(Context ctx) { + var polyCtx = new PolyglotContext(ctx); + polyCtx.getTopScope().compile(true); + } + + private static Module getLoadedModule(Context ctx, String modName) { + var mod = ModuleUtils.getLoadedModule(ctx, modName); + assert mod != null; + return mod; + } + + private static List runExportsResolutionSort(List modules, Context ctx) { + var ensoCtx = ContextUtils.leakContext(ctx); + var compilerCtx = ensoCtx.getCompiler().context(); + var exportsResolution = new ExportsResolution(compilerCtx); + var compilerModules = modules.stream().map(Module::asCompilerModule).toList(); + var sortedCompilerModules = + exportsResolution.runSort(CollectionConverters.asScala(compilerModules).toList()); + return CollectionConverters.asJava(sortedCompilerModules).stream() + .map(Module::fromCompilerModule) + .toList(); + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportStaticMethodTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportStaticMethodTest.java index 8506ee78d9..a0e4f825ae 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportStaticMethodTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportStaticMethodTest.java @@ -32,7 +32,7 @@ public class ExportStaticMethodTest { new SourceModule( QualifiedName.fromString("A_Module"), """ - from project.T_Module export static_method + export project.T_Module.static_method """); var mainMod = new SourceModule( @@ -93,7 +93,7 @@ public class ExportStaticMethodTest { new SourceModule( QualifiedName.fromString("Main"), """ - from project.T_Module export module_method + export project.T_Module.module_method """); var projDir = tempFolder.newFolder().toPath(); ProjectUtils.createProject("Proj", Set.of(tMod, mainMod), projDir); @@ -123,7 +123,7 @@ public class ExportStaticMethodTest { new SourceModule( QualifiedName.fromString("Main"), """ - from project.T_Module export static_method + export project.T_Module.static_method """); var projDir = tempFolder.newFolder().toPath(); ProjectUtils.createProject("Proj", Set.of(tMod, mainMod), projDir); diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/imports/ImportSymbolsTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/imports/ImportSymbolsTest.java new file mode 100644 index 0000000000..57666230e5 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/imports/ImportSymbolsTest.java @@ -0,0 +1,139 @@ +package org.enso.interpreter.test.imports; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Set; +import org.enso.compiler.data.BindingsMap.ResolvedType; +import org.enso.pkg.QualifiedName; +import org.enso.polyglot.PolyglotContext; +import org.enso.polyglot.RuntimeOptions; +import org.enso.test.utils.ContextUtils; +import org.enso.test.utils.ModuleUtils; +import org.enso.test.utils.ProjectUtils; +import org.enso.test.utils.SourceModule; +import org.graalvm.polyglot.PolyglotException; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class ImportSymbolsTest { + @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void importAllFromModuleDoesNotImportModuleItself() throws IOException { + var aMod = + new SourceModule( + QualifiedName.fromString("A_module"), """ + a_mod_method x = x + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + from project.A_module import all + main = + A_Module.a_mod_method 42 + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(aMod, mainMod), projDir); + var out = new ByteArrayOutputStream(); + try (var ctx = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .out(out) + .err(out) + .build()) { + var polyCtx = new PolyglotContext(ctx); + try { + polyCtx.getTopScope().compile(true); + fail("Compilation error expected. out = " + out); + } catch (PolyglotException e) { + assertThat(e.getMessage(), containsString("The name `A_Module` could not be found")); + } + } + } + + @Test + public void importAllFromTypeDoesNotImportTypeItself() throws IOException { + var aMod = + new SourceModule( + QualifiedName.fromString("A_module"), + """ + type A_Type + Cons + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + from project.A_module.A_Type import all + main = + A_Type.Cons + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(aMod, mainMod), projDir); + var out = new ByteArrayOutputStream(); + try (var ctx = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .out(out) + .err(out) + .build()) { + var polyCtx = new PolyglotContext(ctx); + try { + polyCtx.getTopScope().compile(true); + fail("Compilation error expected. Captured output = " + out); + } catch (PolyglotException e) { + assertThat(e.getMessage(), containsString("The name `A_Type` could not be found")); + } + } + } + + // TODO: Tracked by https://github.com/enso-org/enso/issues/10504 + @Ignore + @Test + public void importEntityFromModuleThatExportsItFromOtherModule() throws IOException { + var aMod = + new SourceModule(QualifiedName.fromString("A_Module"), """ + type A_Type + """); + var bMod = + new SourceModule( + QualifiedName.fromString("B_Module"), + """ + export project.A_Module.A_Type + """); + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + import project.B_Module.A_Type + """); + var projDir = tempFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(aMod, bMod, mainMod), projDir); + try (var ctx = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .build()) { + var polyCtx = new PolyglotContext(ctx); + polyCtx.getTopScope().compile(true); + var mainModResolvedImps = ModuleUtils.getResolvedImports(ctx, "local.Proj.Main"); + assertThat(mainModResolvedImps.size(), is(1)); + assertThat( + "There should be only one target of Main's resolved import", + mainModResolvedImps.get(0).targets().size(), + is(1)); + assertThat( + "Resolved import target of Main should point to A_Type", + mainModResolvedImps.get(0).targets().apply(0), + is(instanceOf(ResolvedType.class))); + } + } +} diff --git a/engine/runtime-integration-tests/src/test/resources/Cycle_Test/src/Sub/B.enso b/engine/runtime-integration-tests/src/test/resources/Cycle_Test/src/Sub/B.enso index 341c48af8f..0768ad1a50 100644 --- a/engine/runtime-integration-tests/src/test/resources/Cycle_Test/src/Sub/B.enso +++ b/engine/runtime-integration-tests/src/test/resources/Cycle_Test/src/Sub/B.enso @@ -1 +1 @@ -from project.Sub.C export all +from project.Sub.C export A diff --git a/engine/runtime-integration-tests/src/test/resources/Test_Fully_Qualified_Name_1/src/Main.enso b/engine/runtime-integration-tests/src/test/resources/Test_Fully_Qualified_Name_1/src/Main.enso index 111e633904..5f1580ace5 100644 --- a/engine/runtime-integration-tests/src/test/resources/Test_Fully_Qualified_Name_1/src/Main.enso +++ b/engine/runtime-integration-tests/src/test/resources/Test_Fully_Qualified_Name_1/src/Main.enso @@ -13,4 +13,4 @@ main = Test_Fully_Qualified_Name_1.Synthetic_Mod.A_Mod.A_Type export project.Synthetic_Mod.A_Mod.A_Type -from project.Synthetic_Mod.A_Mod export static_method +export project.Synthetic_Mod.A_Mod.static_method diff --git a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_1/package.yaml b/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_1/package.yaml deleted file mode 100644 index f00b6a028e..0000000000 --- a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_1/package.yaml +++ /dev/null @@ -1,6 +0,0 @@ -name: Test_Multiple_Conflicting_Exports_1 -license: APLv2 -enso-version: default -version: "0.0.1" -author: "Enso Team " -maintainer: "Enso Team " diff --git a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_1/src/F1.enso b/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_1/src/F1.enso deleted file mode 100644 index e7c5649502..0000000000 --- a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_1/src/F1.enso +++ /dev/null @@ -1,2 +0,0 @@ -foo = 42 -bar = "z" diff --git a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_1/src/F2.enso b/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_1/src/F2.enso deleted file mode 100644 index 01c0206e10..0000000000 --- a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_1/src/F2.enso +++ /dev/null @@ -1,3 +0,0 @@ -export project.F1 -from project.F1 export foo -from project.F1 export all hiding foo diff --git a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_1/src/Main.enso b/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_1/src/Main.enso deleted file mode 100644 index 6951b9d329..0000000000 --- a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_1/src/Main.enso +++ /dev/null @@ -1,7 +0,0 @@ -from Standard.Base import IO -from project.F2 import all - -main = - IO.println F1.bar - IO.println foo - diff --git a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_2/package.yaml b/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_2/package.yaml deleted file mode 100644 index 69ee40fa07..0000000000 --- a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_2/package.yaml +++ /dev/null @@ -1,6 +0,0 @@ -name: Test_Multiple_Conflicting_Exports_2 -license: APLv2 -enso-version: default -version: "0.0.1" -author: "Enso Team " -maintainer: "Enso Team " diff --git a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_2/src/F1.enso b/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_2/src/F1.enso deleted file mode 100644 index e7c5649502..0000000000 --- a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_2/src/F1.enso +++ /dev/null @@ -1,2 +0,0 @@ -foo = 42 -bar = "z" diff --git a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_2/src/F2.enso b/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_2/src/F2.enso deleted file mode 100644 index a01b3e3d49..0000000000 --- a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_2/src/F2.enso +++ /dev/null @@ -1,3 +0,0 @@ -export project.F1 -from project.F1 export all -from project.F1 export all hiding bar diff --git a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_2/src/Main.enso b/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_2/src/Main.enso deleted file mode 100644 index 6951b9d329..0000000000 --- a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Conflicting_Exports_2/src/Main.enso +++ /dev/null @@ -1,7 +0,0 @@ -from Standard.Base import IO -from project.F2 import all - -main = - IO.println F1.bar - IO.println foo - diff --git a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Exports/src/F2.enso b/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Exports/src/F2.enso index d41d90c53a..8f665d09ca 100644 --- a/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Exports/src/F2.enso +++ b/engine/runtime-integration-tests/src/test/resources/Test_Multiple_Exports/src/F2.enso @@ -1,2 +1,2 @@ export project.F1 -from project.F1 export foo +export project.F1.foo diff --git a/engine/runtime-integration-tests/src/test/resources/Test_Private_Modules_1/src/Main.enso b/engine/runtime-integration-tests/src/test/resources/Test_Private_Modules_1/src/Main.enso index d17abbeacc..bf09126bae 100644 --- a/engine/runtime-integration-tests/src/test/resources/Test_Private_Modules_1/src/Main.enso +++ b/engine/runtime-integration-tests/src/test/resources/Test_Private_Modules_1/src/Main.enso @@ -1,8 +1,8 @@ # Export some "public" stuff from this library -from project.Pub_Mod export Pub_Mod_Type +export project.Pub_Mod.Pub_Mod_Type # Can import and use private modules in the same project -from project.Priv_Mod import Type_In_Priv_Mod +import project.Priv_Mod.Type_In_Priv_Mod main = Type_In_Priv_Mod.Value 42 . data diff --git a/engine/runtime-integration-tests/src/test/resources/Test_Type_Exports/src/Type_Exp.enso b/engine/runtime-integration-tests/src/test/resources/Test_Type_Exports/src/Type_Exp.enso index c281090a49..ad1ba20f94 100644 --- a/engine/runtime-integration-tests/src/test/resources/Test_Type_Exports/src/Type_Exp.enso +++ b/engine/runtime-integration-tests/src/test/resources/Test_Type_Exports/src/Type_Exp.enso @@ -1 +1 @@ -from project.Type_Def.Maybe export Some +export project.Type_Def.Maybe.Some diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/PassesTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/PassesTest.scala index fa35214170..2f25f41bdd 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/PassesTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/PassesTest.scala @@ -9,7 +9,6 @@ import org.enso.compiler.pass.analyse.{ AliasAnalysis, AmbiguousImportsAnalysis, BindingAnalysis, - ExportSymbolAnalysis, ImportSymbolAnalysis, PrivateConstructorAnalysis, PrivateModuleAnalysis @@ -65,7 +64,6 @@ class PassesTest extends CompilerTest { AmbiguousImportsAnalysis, PrivateModuleAnalysis.INSTANCE, PrivateConstructorAnalysis.INSTANCE, - ExportSymbolAnalysis.INSTANCE, ShadowedPatternFields, UnreachableMatchBranches, NestedPatternMatch, 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 3d61b6a413..8342d7245f 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 @@ -9,10 +9,10 @@ import org.enso.compiler.data.BindingsMap.{ Argument, Cons, ConversionMethod, + ExtensionMethod, ModuleMethod, PolyglotSymbol, - ResolvedStaticMethod, - StaticMethod, + ResolvedExtensionMethod, Type } import org.enso.compiler.pass.analyse.BindingAnalysis @@ -68,14 +68,14 @@ class BindingAnalysisTest extends CompilerTest { metadata.definedEntities should contain theSameElementsAs List( Type("My_Type", List(), List(), false), - StaticMethod("extension_method", "My_Type") + ExtensionMethod("extension_method", "My_Type") ) metadata.resolveName("extension_method") shouldEqual Right( List( - ResolvedStaticMethod( + ResolvedExtensionMethod( ctx.moduleReference(), - StaticMethod("extension_method", "My_Type") + ExtensionMethod("extension_method", "My_Type") ) ) ) @@ -95,19 +95,19 @@ class BindingAnalysisTest extends CompilerTest { metadata.definedEntities should contain theSameElementsAs List( Type("My_Type", List(), List(), false), Type("Other_Type", List(), List(), false), - StaticMethod("extension_method", "My_Type"), - StaticMethod("extension_method", "Other_Type") + ExtensionMethod("extension_method", "My_Type"), + ExtensionMethod("extension_method", "Other_Type") ) metadata.resolveName("extension_method") shouldBe Right( List( - ResolvedStaticMethod( + ResolvedExtensionMethod( ctx.moduleReference(), - StaticMethod("extension_method", "My_Type") + ExtensionMethod("extension_method", "My_Type") ), - ResolvedStaticMethod( + ResolvedExtensionMethod( ctx.moduleReference(), - StaticMethod("extension_method", "Other_Type") + ExtensionMethod("extension_method", "Other_Type") ) ) ) @@ -189,8 +189,8 @@ class BindingAnalysisTest extends CompilerTest { ), Type("Bar", List(), List(), builtinType = false), Type("Baz", List("x", "y"), List(), builtinType = false), - StaticMethod("foo", "Baz"), - StaticMethod("baz", "Bar"), + ExtensionMethod("foo", "Baz"), + ExtensionMethod("baz", "Bar"), ConversionMethod("from", "Bar", "Foo"), ModuleMethod("foo") ) @@ -216,9 +216,9 @@ class BindingAnalysisTest extends CompilerTest { .filter( _.isInstanceOf[BindingsMap.Method] ) should contain theSameElementsAs List( - StaticMethod("foo", moduleName), + ExtensionMethod("foo", moduleName), ModuleMethod("bar"), - StaticMethod("baz", moduleName) + ExtensionMethod("baz", moduleName) ) } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/desugar/ImportsTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/desugar/ImportsTest.scala index 8d7064cd99..10f95761cf 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/desugar/ImportsTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/desugar/ImportsTest.scala @@ -57,7 +57,6 @@ class ImportsTest extends CompilerTest { |export Bar.Foo |export Bar.Foo as Bar |from Bar.Foo export Bar, Baz - |from Bar.Foo export all | |import Foo.Bar.Baz |export Foo.Bar.Baz @@ -73,11 +72,10 @@ class ImportsTest extends CompilerTest { } "desugar project name exports correctly" in { - ir.exports.take(4).map(_.showCode()) shouldEqual List( + ir.exports.take(3).map(_.showCode()) shouldEqual List( "export Bar.Foo.Main as Foo", "export Bar.Foo.Main as Bar", - "from Bar.Foo.Main export Bar, Baz", - "from Bar.Foo.Main export all" + "from Bar.Foo.Main export Bar, Baz" ) } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/GlobalNamesTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/GlobalNamesTest.scala index 1669d6397c..24d6d18ca0 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/GlobalNamesTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/GlobalNamesTest.scala @@ -13,7 +13,7 @@ import org.enso.compiler.core.ir.expression.errors import org.enso.compiler.data.BindingsMap.{Resolution, ResolvedModule} import org.enso.compiler.pass.resolve.GlobalNames import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager} -import org.enso.compiler.phase.ExportsResolution +import org.enso.compiler.phase.exports.ExportsResolution import org.enso.compiler.test.CompilerTest import org.enso.interpreter.runtime import org.enso.interpreter.runtime.ModuleTestUtils diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala index 75054eace9..01fe22d786 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala @@ -5,6 +5,7 @@ import org.enso.compiler.core.ir.{Module, ProcessingPass, Warning} import org.enso.compiler.core.ir.expression.errors import org.enso.compiler.core.ir.module.scope.Import import org.enso.compiler.data.BindingsMap +import org.enso.compiler.phase.exports.ExportsResolution import org.enso.compiler.pass.analyse.{BindingAnalysis, GatherDiagnostics} import org.enso.interpreter.runtime import org.enso.interpreter.runtime.EnsoContext @@ -12,6 +13,7 @@ import org.enso.persist.Persistance import org.enso.pkg.QualifiedName import org.enso.common.LanguageInfo import org.enso.common.MethodNames +import org.enso.compiler.phase.exports.Node import org.enso.polyglot.RuntimeOptions import org.graalvm.polyglot.{Context, Engine} import org.scalatest.BeforeAndAfter @@ -89,6 +91,29 @@ class ImportExportTest } } + private def buildExportsGraph( + modules: List[org.enso.interpreter.runtime.Module] + ): List[Node] = { + val compilerCtx = langCtx.getCompiler.context + val exportsResolution = new ExportsResolution(compilerCtx) + val compilerModules = modules.map(_.asCompilerModule()) + exportsResolution.buildModuleGraph(compilerModules) + } + + /** Just delegates to [[ExportsResolution.runSort()]] + */ + private def runExportsResolutionSort( + modules: List[org.enso.interpreter.runtime.Module] + ): List[org.enso.interpreter.runtime.Module] = { + val compilerCtx = langCtx.getCompiler.context + val exportsResolution = new ExportsResolution(compilerCtx) + val compilerModules = modules.map(_.asCompilerModule()) + val sortedCompilerModules = exportsResolution.runSort(compilerModules) + sortedCompilerModules.map( + org.enso.interpreter.runtime.Module.fromCompilerModule + ) + } + before { ctx.enter() } @@ -139,7 +164,8 @@ class ImportExportTest mainBindingsMap.resolvedImports.size shouldEqual 1 mainBindingsMap .resolvedImports(0) - .target + .targets + .head .isInstanceOf[BindingsMap.ResolvedModule] shouldBe true } @@ -168,7 +194,8 @@ class ImportExportTest mainIr.unwrapBindingMap.resolvedImports.size shouldEqual 1 mainIr.unwrapBindingMap .resolvedImports(0) - .target + .targets + .head .isInstanceOf[BindingsMap.ResolvedModule] shouldBe true } @@ -230,53 +257,130 @@ class ImportExportTest ) } - // TODO[pm]: will be addressed in https://github.com/enso-org/enso/issues/6729 - "resolve static method from a module" ignore { + "resolve module method" in { """ - |static_method = + |module_method = | 42 |""".stripMargin .createModule(packageQualifiedName.createChild("A_Module")) - val bIr = - s""" - |import $namespace.$packageName.A_Module.static_method - |""".stripMargin - .createModule(packageQualifiedName.createChild("B_Module")) - .getIr val mainIr = s""" - |from $namespace.$packageName.A_Module import static_method + |import $namespace.$packageName.A_Module.module_method |""".stripMargin .createModule(packageQualifiedName.createChild("Main")) .getIr - mainIr.imports.head.isInstanceOf[errors.ImportExport] shouldBe false - bIr.imports.head.isInstanceOf[errors.ImportExport] shouldBe false val mainBindingMap = mainIr.unwrapBindingMap - val bBindingMap = bIr.unwrapBindingMap - mainBindingMap.resolvedImports.size shouldEqual 2 - mainBindingMap - .resolvedImports(0) - .target - .asInstanceOf[BindingsMap.ResolvedModule] - .module - .getName - .item shouldEqual "A_Module" - mainBindingMap - .resolvedImports(1) - .target + mainBindingMap.resolvedImports.size shouldEqual 1 + mainBindingMap.resolvedImports.head.targets.head .asInstanceOf[BindingsMap.ResolvedModuleMethod] .method - .name shouldEqual "static_method" - // In B_Module, we only have ResolvedMethod in the resolvedImports, there is no ResolvedModule - // But that does not matter. - bBindingMap.resolvedImports.size shouldEqual 1 - bBindingMap - .resolvedImports(0) - .target - .asInstanceOf[BindingsMap.ResolvedModuleMethod] - .method - .name shouldEqual "static_method" + .name shouldEqual "module_method" + } + + "be able to import constructor from type" in { + """ + |type My_Type + | Constructor + |""".stripMargin + .createModule(packageQualifiedName.createChild("My_Module")) + + val mainIr = + s""" + |import $namespace.$packageName.My_Module.My_Type.Constructor + |""".stripMargin + .createModule(packageQualifiedName.createChild("Main")) + .getIr + val bindingMap = mainIr.unwrapBindingMap + bindingMap.resolvedImports.size shouldBe 1 + bindingMap.resolvedImports.head.targets.head shouldBe a[ + BindingsMap.ResolvedConstructor + ] + + bindingMap.resolvedImports.head.targets.head + .asInstanceOf[BindingsMap.ResolvedConstructor] + .qualifiedName + .item shouldBe "Constructor" + } + + "be able to export constructor from type" in { + """ + |type Boolean + | True + | False + |""".stripMargin + .createModule(packageQualifiedName.createChild("Boolean")) + + val mainIr = + s""" + |export $namespace.$packageName.Boolean.Boolean.True + |export $namespace.$packageName.Boolean.Boolean.False + |""".stripMargin + .createModule(packageQualifiedName.createChild("Main")) + .getIr + val bindingMap = mainIr.unwrapBindingMap + bindingMap.exportedSymbols.keys should contain theSameElementsAs List( + "True", + "False" + ) + bindingMap + .exportedSymbols("True") + .head + .isInstanceOf[BindingsMap.ResolvedConstructor] shouldBe true + bindingMap + .exportedSymbols("False") + .head + .isInstanceOf[BindingsMap.ResolvedConstructor] shouldBe true + } + + "import single static method with one symbol" in { + """ + |type My_Type + |My_Type.extension_method x = x + |""".stripMargin + .createModule(packageQualifiedName.createChild("Module")) + + val mainIr = s""" + |import $namespace.$packageName.Module.extension_method + |""".stripMargin + .createModule(packageQualifiedName.createChild("Main")) + .getIr + mainIr.imports.head + .isInstanceOf[errors.ImportExport] shouldBe false + + val bindingsMap = mainIr.unwrapBindingMap + bindingsMap.resolvedImports.size shouldBe 1 + val resolvedImport = bindingsMap.resolvedImports.head + resolvedImport.targets.head shouldBe a[ + BindingsMap.ResolvedExtensionMethod + ] + resolvedImport.targets.head + .asInstanceOf[BindingsMap.ResolvedExtensionMethod] + .staticMethod + .methodName shouldBe "extension_method" + } + + "export multiple static methods with one symbol" in { + """ + |type My_Type + |type Other_Type + | + |My_Type.extension_method x = x + |Other_Type.extension_method y = y + |""".stripMargin + .createModule(packageQualifiedName.createChild("Module")) + + val mainIr = s""" + |export $namespace.$packageName.Module.extension_method + |""".stripMargin + .createModule(packageQualifiedName.createChild("Main")) + .getIr + mainIr.imports.head + .isInstanceOf[errors.ImportExport] shouldBe false + val bm = mainIr.unwrapBindingMap + bm.exportedSymbols.size shouldBe 1 + bm.exportedSymbols.get("extension_method") shouldBe defined + bm.exportedSymbols("extension_method").size shouldBe 2 } "result in error when trying to import mix of constructors and methods from a type" in { @@ -317,8 +421,8 @@ class ImportExportTest mainIr.imports.head .asInstanceOf[errors.ImportExport] .reason - .asInstanceOf[errors.ImportExport.ModuleDoesNotExist] - .name should include("static_method") + .asInstanceOf[errors.ImportExport.IllegalImportFromMethod] + .methodName should include("static_method") } "result in error when trying to import anything from a non-existing symbol" in { @@ -339,63 +443,6 @@ class ImportExportTest .name should include("Non_Existing_Symbol") } - // TODO[pm]: Fix in https://github.com/enso-org/enso/issues/6669 - "resolve all symbols from transitively exported type" ignore { - """ - |type A_Type - | A_Constructor - | a_method self = 1 - |""".stripMargin - .createModule(packageQualifiedName.createChild("A_Module")) - s""" - |import $namespace.$packageName.A_Module.A_Type - |export $namespace.$packageName.A_Module.A_Type - |""".stripMargin - .createModule(packageQualifiedName.createChild("B_Module")) - val mainIr = - s""" - |from $namespace.$packageName.B_Module.A_Type import all - |""".stripMargin - .createModule(packageQualifiedName.createChild("Main")) - .getIr - mainIr.imports.size shouldEqual 1 - mainIr.imports.head.isInstanceOf[errors.ImportExport] shouldBe false - val mainBindingMap = mainIr.unwrapBindingMap - val resolvedImportTargets = - mainBindingMap.resolvedImports.map(_.target) - resolvedImportTargets - .collect { case c: BindingsMap.ResolvedConstructor => c } - .map(_.cons.name) should contain theSameElementsAs List("A_Constructor") - } - - // TODO[pm]: Fix in https://github.com/enso-org/enso/issues/6669 - "resolve constructor from transitively exported type" ignore { - """ - |type A_Type - | A_Constructor - |""".stripMargin - .createModule(packageQualifiedName.createChild("A_Module")) - s""" - |import $namespace.$packageName.A_Module.A_Type - |export $namespace.$packageName.A_Module.A_Type - |""".stripMargin - .createModule(packageQualifiedName.createChild("B_Module")) - val mainIr = - s""" - |from $namespace.$packageName.B_Module.A_Type import A_Constructor - |""".stripMargin - .createModule(packageQualifiedName.createChild("Main")) - .getIr - mainIr.imports.size shouldEqual 1 - mainIr.imports.head.isInstanceOf[errors.ImportExport] shouldBe false - val mainBindingMap = mainIr.unwrapBindingMap - val resolvedImportTargets = - mainBindingMap.resolvedImports.map(_.target) - resolvedImportTargets - .collect { case c: BindingsMap.ResolvedConstructor => c } - .map(_.cons.name) should contain theSameElementsAs List("A_Constructor") - } - "export is not transitive" in { s""" |import $namespace.$packageName.A_Module.A_Type @@ -442,10 +489,6 @@ class ImportExportTest mainModule.getIr.imports.head .asInstanceOf[Import.Module] .isSynthetic shouldBe true - - val resolvedExports = mainModule.getIr.unwrapBindingMap.resolvedExports - resolvedExports.size shouldBe 1 - resolvedExports.head.target.qualifiedName.item shouldBe "A_Type" } "(from) export type without import should insert synthetic import" in { @@ -463,9 +506,6 @@ class ImportExportTest mainModule.getIr.imports.head .asInstanceOf[Import.Module] .isSynthetic shouldBe true - - val resolvedExports = mainModule.getIr.unwrapBindingMap.resolvedExports - resolvedExports.size shouldBe 1 } "export module without import should insert synthetic import" in { @@ -483,10 +523,6 @@ class ImportExportTest mainModule.getIr.imports.head .asInstanceOf[Import.Module] .isSynthetic shouldBe true - - val resolvedExports = mainModule.getIr.unwrapBindingMap.resolvedExports - resolvedExports.size shouldBe 1 - resolvedExports.head.target.qualifiedName.item shouldBe "A_Module" } "export unknown type without import should result in error" in { @@ -525,12 +561,12 @@ class ImportExportTest mainIr.exports.head .asInstanceOf[errors.ImportExport] .reason - .isInstanceOf[errors.ImportExport.SymbolDoesNotExist] shouldBe true + .isInstanceOf[errors.ImportExport.NoSuchConstructor] shouldBe true mainIr.exports.head .asInstanceOf[errors.ImportExport] .reason - .asInstanceOf[errors.ImportExport.SymbolDoesNotExist] - .symbolName shouldEqual "Non_Existing_Ctor" + .asInstanceOf[errors.ImportExport.NoSuchConstructor] + .constructorName shouldEqual "Non_Existing_Ctor" } "fail when exporting from other module" in { @@ -547,8 +583,6 @@ class ImportExportTest .createModule(packageQualifiedName.createChild("Main")) .getIr - val bindingsMap = mainIr.unwrapBindingMap - bindingsMap shouldNot be(null) mainIr.exports.size shouldEqual 1 mainIr.exports.head.isInstanceOf[errors.ImportExport] shouldBe true mainIr.exports.head @@ -582,12 +616,12 @@ class ImportExportTest mainIr.exports.head .asInstanceOf[errors.ImportExport] .reason - .isInstanceOf[errors.ImportExport.SymbolDoesNotExist] shouldBe true + .isInstanceOf[errors.ImportExport.NoSuchConstructor] shouldBe true mainIr.exports.head .asInstanceOf[errors.ImportExport] .reason - .asInstanceOf[errors.ImportExport.SymbolDoesNotExist] - .symbolName shouldEqual "Non_Existing_Ctor" + .asInstanceOf[errors.ImportExport.NoSuchConstructor] + .constructorName shouldEqual "Non_Existing_Ctor" } "fail when exporting from type" in { @@ -610,12 +644,12 @@ class ImportExportTest mainIr.exports.head .asInstanceOf[errors.ImportExport] .reason - .isInstanceOf[errors.ImportExport.SymbolDoesNotExist] shouldBe true + .isInstanceOf[errors.ImportExport.NoSuchConstructor] shouldBe true mainIr.exports.head .asInstanceOf[errors.ImportExport] .reason - .asInstanceOf[errors.ImportExport.SymbolDoesNotExist] - .symbolName shouldEqual "FOO" + .asInstanceOf[errors.ImportExport.NoSuchConstructor] + .constructorName shouldEqual "FOO" } "fail when exporting from module with `from`" in { @@ -1064,21 +1098,23 @@ class ImportExportTest |# it is also considered a static module method |glob_var = 42 | - |# This is also a static method - |foreign js js_function = \"\"\" - | return 42 |""".stripMargin .createModule(packageQualifiedName.createChild("A_Module")) - s""" - |from $namespace.$packageName.A_Module import all - |from $namespace.$packageName.A_Module export static_method - | - |type B_Type - | B_Constructor val - |""".stripMargin + val bIr = s""" + |from $namespace.$packageName.A_Module import all + |from $namespace.$packageName.A_Module export static_method + | + |type B_Type + | B_Constructor val + |""".stripMargin .createModule(packageQualifiedName.createChild("B_Module")) .getIr + val bBindingMap = bIr.unwrapBindingMap + bBindingMap.exportedSymbols.keys should contain theSameElementsAs List( + "static_method", + "B_Type" + ) val mainIr = s""" @@ -1330,4 +1366,308 @@ class ImportExportTest diags.size shouldEqual 0 } } + + "Exports graph building" should { + def assertAModExportedByBMod(graph: List[Node]): Unit = { + val aModNode = graph.find(node => + node.module match { + case BindingsMap.ResolvedModule(modRef) => + modRef.getName.item == "A_Module" + case _ => false + } + ) + aModNode shouldBe defined + val aModNodeExporter = + aModNode.get.exportedBy.head.exporter + withClue("A_Module should be exported by B_Module") { + aModNodeExporter.module + .isInstanceOf[BindingsMap.ResolvedModule] shouldBe true + aModNodeExporter.module + .asInstanceOf[BindingsMap.ResolvedModule] + .qualifiedName + .item shouldBe "B_Module" + } + } + + // Directly export a single module + "build exports graph for a module" in { + val aModule = + """ + |# Blank on purpose + |""".stripMargin + .createModule(packageQualifiedName.createChild("A_Module")) + + val bModule = + s""" + |export $namespace.$packageName.A_Module + |""".stripMargin + .createModule(packageQualifiedName.createChild("B_Module")) + val graph = buildExportsGraph(List(aModule, bModule)) + withClue("There should be only A_Module and B_Module nodes") { + graph.size shouldBe 2 + } + assertAModExportedByBMod(graph) + } + + "build exports graph for module method with `from ... export ...` syntax" in { + val aModule = + """ + |type A_Type + | A_Constructor + | instance_method self = 42 + | + |module_method = + | local_var = 42 + | local_var + |""".stripMargin + .createModule(packageQualifiedName.createChild("A_Module")) + + val bModule = + s""" + |from $namespace.$packageName.A_Module export module_method + | + |type B_Type + | B_Constructor val + |""".stripMargin + .createModule(packageQualifiedName.createChild("B_Module")) + val graph = buildExportsGraph(List(aModule, bModule)) + withClue("There should be only A_Module and B_Module nodes") { + graph.size shouldBe 2 + } + assertAModExportedByBMod(graph) + } + + "build exports graph for module method with `export ...` syntax" in { + val aModule = + """ + |type A_Type + | A_Constructor + | instance_method self = 42 + | + |module_method = + | local_var = 42 + | local_var + |""".stripMargin + .createModule(packageQualifiedName.createChild("A_Module")) + + val bModule = + s""" + |export $namespace.$packageName.A_Module.module_method + | + |type B_Type + | B_Constructor val + |""".stripMargin + .createModule(packageQualifiedName.createChild("B_Module")) + val graph = buildExportsGraph(List(aModule, bModule)) + withClue("There should be only A_Module and B_Module nodes") { + graph.size shouldBe 2 + } + assertAModExportedByBMod(graph) + } + + "build exports graph for type" in { + val aModule = + """ + |type A_Type + | A_Constructor + | instance_method self = 42 + | + |static_method = + | local_var = 42 + | local_var + |""".stripMargin + .createModule(packageQualifiedName.createChild("A_Module")) + + val bModule = + s""" + |from $namespace.$packageName.A_Module export A_Type + | + |type B_Type + | B_Constructor val + |""".stripMargin + .createModule(packageQualifiedName.createChild("B_Module")) + val graph = buildExportsGraph(List(aModule, bModule)) + withClue("There should be only A_Module and B_Module nodes") { + graph.size shouldBe 2 + } + assertAModExportedByBMod(graph) + } + + "build exports graph for constructors" in { + val boolModule = + """ + |type Boolean + | True + | False + |""".stripMargin + .createModule(packageQualifiedName.createChild("Boolean")) + + val mainModule = + s""" + |export $namespace.$packageName.Boolean.Boolean.True + |export $namespace.$packageName.Boolean.Boolean.False + |""".stripMargin + .createModule(packageQualifiedName.createChild("Main")) + + val graph = buildExportsGraph(List(boolModule, mainModule)) + withClue( + "graph should contains node for: [Boolean, Main]" + ) { + graph.size shouldBe 2 + } + val boolNode = graph.find(_.module.qualifiedName.item == "Boolean") + boolNode shouldBe defined + val boolExporter = boolNode.get.exportedBy.head.exporter + boolExporter.module.qualifiedName.item shouldBe "Main" + } + + "No exports graph is constructed when only import is used" in { + val aModule = + """ + |type A_Type + | A_Constructor + |""".stripMargin + .createModule(packageQualifiedName.createChild("A_Module")) + + val bModule = + s""" + |from $namespace.$packageName.A_Module import A_Type + | + |main = + | A_Type.A_Constructor + |""".stripMargin + .createModule(packageQualifiedName.createChild("B_Module")) + val graph = buildExportsGraph(List(aModule, bModule)) + withClue("Only two modules are defined") { + graph.size shouldBe 2 + } + withClue("There should be no exports") { + graph.forall(node => node.exports.isEmpty) shouldBe true + } + } + } + + "Export resolution sorting" should { + "correctly sort two modules" in { + val aModule = + """ + |type A_Type + |""".stripMargin + .createModule(packageQualifiedName.createChild("A_Module")) + + val bModule = + s""" + |export $namespace.$packageName.A_Module.A_Type + |""".stripMargin + .createModule(packageQualifiedName.createChild("B_Module")) + val sortedMods = runExportsResolutionSort(List(aModule, bModule)) + sortedMods should contain theSameElementsInOrderAs List( + aModule, + bModule + ) + } + + "correctly sort three modules with two independent modules" in { + val aModule = + """ + |type A_Type + |""".stripMargin + .createModule(packageQualifiedName.createChild("A_Module")) + + val bModule = + s""" + |type B_Type + |""".stripMargin + .createModule(packageQualifiedName.createChild("B_Module")) + + val cModule = + s""" + |export $namespace.$packageName.A_Module.A_Type + |export $namespace.$packageName.B_Module.B_Type + |""".stripMargin + .createModule(packageQualifiedName.createChild("C_Module")) + val sortedMods = runExportsResolutionSort(List(aModule, bModule, cModule)) + sortedMods.last shouldBe cModule + sortedMods.take(2) should contain theSameElementsAs List( + aModule, + bModule + ) + } + + "correctly sort three modules with transitive exports" in { + val aModule = + """ + |# blank on purpose + |""".stripMargin + .createModule(packageQualifiedName.createChild("A_Module")) + + val bModule = + s""" + |export $namespace.$packageName.A_Module + |""".stripMargin + .createModule(packageQualifiedName.createChild("B_Module")) + + val cModule = + s""" + |export $namespace.$packageName.B_Module + |""".stripMargin + .createModule(packageQualifiedName.createChild("C_Module")) + val sortedMods = runExportsResolutionSort(List(aModule, bModule, cModule)) + sortedMods should contain theSameElementsInOrderAs List( + aModule, + bModule, + cModule + ) + } + + "correctly sort two modules with exported module method" in { + val aModule = + """ + |module_method = + | 42 + |""".stripMargin + .createModule(packageQualifiedName.createChild("A_Module")) + + val bModule = + s""" + |export $namespace.$packageName.A_Module.module_method + |""".stripMargin + .createModule(packageQualifiedName.createChild("B_Module")) + val sortedMods = runExportsResolutionSort(List(aModule, bModule)) + sortedMods should contain theSameElementsInOrderAs List( + aModule, + bModule + ) + } + + "correctly sort three modules with exported module method and import" in { + val aModule = + """ + |module_method = + | 42 + |""".stripMargin + .createModule(packageQualifiedName.createChild("A_Module")) + + val bModule = + s""" + |export $namespace.$packageName.A_Module.module_method + |""".stripMargin + .createModule(packageQualifiedName.createChild("B_Module")) + + val cModule = + s""" + |from $namespace.$packageName.B_Module import all + |""".stripMargin + .createModule(packageQualifiedName.createChild("C_Module")) + + val sortedMods = runExportsResolutionSort(List(aModule, bModule, cModule)) + withClue( + "A_Module should always be before B_Module.C_Module can be anywhere" + ) { + val aModIdx = sortedMods.indexOf(aModule) + val bModIdx = sortedMods.indexOf(bModule) + aModIdx should be < bModIdx + } + } + } } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala index cba002ab1e..9e9ca0f859 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala @@ -9,6 +9,7 @@ import org.enso.polyglot.ModuleExports import org.enso.polyglot.Suggestion import org.enso.polyglot.data.Tree import org.enso.polyglot.runtime.Runtime.Api +import org.enso.polyglot.runtime.Runtime.Api.SuggestionAction import org.enso.text.editing.model import org.enso.text.editing.model.TextEdit import org.graalvm.polyglot.Context @@ -20,7 +21,6 @@ import java.io.{ByteArrayOutputStream, File} import java.nio.file.{Files, Paths} import java.util.UUID import java.util.logging.Level - import scala.collection.immutable.ListSet @scala.annotation.nowarn("msg=multiarg infix syntax") @@ -1015,9 +1015,12 @@ class RuntimeSuggestionUpdatesTest """from Standard.Base import all | |import Enso_Test.Test.A - |from Enso_Test.Test.A export all |import Enso_Test.Test.A.MyType - |from Enso_Test.Test.A.MyType export all + | + |export Enso_Test.Test.A.MyType # Line 5 + |export Enso_Test.Test.A.fortytwo + |export Enso_Test.Test.A.hello # This line will be removed + |export Enso_Test.Test.A.MyType.MkA | |main = IO.println "Hello World!" |""".stripMargin.linesIterator.mkString("\n") @@ -1073,6 +1076,55 @@ class RuntimeSuggestionUpdatesTest 5 ) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), + Api.Response( + Api.SuggestionsDatabaseModuleUpdateNotification( + module = "Enso_Test.Test.Main", + actions = + Vector(Api.SuggestionsDatabaseAction.Clean("Enso_Test.Test.Main")), + exports = Vector( + Api.ExportsUpdate( + ModuleExports( + "Enso_Test.Test.Main", + ListSet( + ExportedSymbol.Type("Enso_Test.Test.A", "MyType"), + ExportedSymbol.Constructor("Enso_Test.Test.A", "MkA"), + ExportedSymbol.Method("Enso_Test.Test.Main", "main"), + ExportedSymbol.Method("Enso_Test.Test.A", "hello") + ) + ), + Api.ExportsAction.Add() + ) + ), + updates = Tree.Root( + Vector( + Tree.Node( + Api.SuggestionUpdate( + Suggestion.Module("Enso_Test.Test.Main", None), + Api.SuggestionAction.Add() + ), + Vector() + ), + Tree.Node( + Api.SuggestionUpdate( + Suggestion.DefinedMethod( + None, + "Enso_Test.Test.Main", + "main", + List(), + "Enso_Test.Test.Main", + ConstantsGen.ANY, + true, + None, + Seq() + ), + Api.SuggestionAction.Add() + ), + Vector() + ) + ) + ) + ) + ), Api.Response( Api.SuggestionsDatabaseModuleUpdateNotification( module = "Enso_Test.Test.A", @@ -1094,7 +1146,10 @@ class RuntimeSuggestionUpdatesTest Vector( Tree.Node( Api.SuggestionUpdate( - Suggestion.Module("Enso_Test.Test.A", None), + Suggestion.Module( + "Enso_Test.Test.A", + None + ), Api.SuggestionAction.Add() ), Vector() @@ -1105,7 +1160,7 @@ class RuntimeSuggestionUpdatesTest None, "Enso_Test.Test.A", "MyType", - List(), + Seq(), "Enso_Test.Test.A.MyType", Some(ConstantsGen.ANY), None @@ -1121,14 +1176,19 @@ class RuntimeSuggestionUpdatesTest "Enso_Test.Test.A", "MkA", List( - Suggestion - .Argument("a", ConstantsGen.ANY, false, false, None) + Suggestion.Argument( + "a", + ConstantsGen.ANY, + false, + false, + None + ) ), "Enso_Test.Test.A.MyType", None, Seq() ), - Api.SuggestionAction.Add() + SuggestionAction.Add() ), Vector() ), @@ -1138,22 +1198,21 @@ class RuntimeSuggestionUpdatesTest None, "Enso_Test.Test.A", "a", - List( - Suggestion - .Argument( - "self", - "Enso_Test.Test.A.MyType", - false, - false, - None - ) + Seq( + Suggestion.Argument( + "self", + "Enso_Test.Test.A.MyType", + false, + false, + None + ) ), "Enso_Test.Test.A.MyType", ConstantsGen.ANY, None, Seq() ), - Api.SuggestionAction.Add() + SuggestionAction.Add() ), Vector() ), @@ -1203,71 +1262,21 @@ class RuntimeSuggestionUpdatesTest ) ) ), - Api.Response( - Api.SuggestionsDatabaseModuleUpdateNotification( - module = moduleName, - actions = Vector(Api.SuggestionsDatabaseAction.Clean(moduleName)), - exports = Vector( - Api.ExportsUpdate( - ModuleExports( - "Enso_Test.Test.Main", - ListSet( - ExportedSymbol.Type("Enso_Test.Test.A", "MyType"), - ExportedSymbol.Constructor("Enso_Test.Test.A", "MkA"), - ExportedSymbol.Method(moduleName, "main"), - ExportedSymbol.Method("Enso_Test.Test.A", "hello") - ) - ), - Api.ExportsAction.Add() - ) - ), - updates = Tree.Root( - Vector( - Tree.Node( - Api.SuggestionUpdate( - Suggestion.Module( - moduleName, - None - ), - Api.SuggestionAction.Add() - ), - Vector() - ), - Tree.Node( - Api.SuggestionUpdate( - Suggestion.DefinedMethod( - None, - moduleName, - "main", - List(), - "Enso_Test.Test.Main", - ConstantsGen.ANY, - true, - None, - Seq() - ), - Api.SuggestionAction.Add() - ), - Vector() - ) - ) - ) - ) - ), Api.Response(Api.AnalyzeModuleInScopeJobFinished()), context.executionComplete(contextId) ) context.consumeOut shouldEqual List("Hello World!") - // Modify the file + // Modify the file - remove the export of `hello` method + // Remove the line `export Enso_Test.Test.A.hello` context.send( Api.Request( Api.EditFileNotification( mainFile, Seq( TextEdit( - model.Range(model.Position(3, 32), model.Position(3, 32)), - " hiding hello" + model.Range(model.Position(7, 0), model.Position(8, 0)), + "" ) ), execute = true, @@ -1298,14 +1307,14 @@ class RuntimeSuggestionUpdatesTest ) context.consumeOut shouldEqual List("Hello World!") - // Modify the file + // Modify the file - remove all exports context.send( Api.Request( Api.EditFileNotification( mainFile, Seq( TextEdit( - model.Range(model.Position(2, 0), model.Position(7, 0)), + model.Range(model.Position(5, 0), model.Position(8, 0)), "" ) ), diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/semantic/ImportsTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/semantic/ImportsTest.scala index 0c76e1cd51..a6aefda1c4 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/semantic/ImportsTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/semantic/ImportsTest.scala @@ -128,12 +128,13 @@ class ImportsTest extends PackageTest { } "Compiler" should "detect name conflicts preventing users from importing submodules" in { - the[InterpreterException] thrownBy evalTestProject( - "Test_Submodules_Name_Conflict" - ) should have message "Method `c_mod_method` of type C.type could not be found." - val outLines = consumeOut - outLines(1) should include - "Declaration of type C shadows module local.Test_Submodules_Name_Conflict.A.B.C making it inaccessible via a qualified name." + try { + evalTestProject("Test_Submodules_Name_Conflict") + fail("Should throw CompilerError") + } catch { + case e: InterpreterException => + e.getMessage.contains("Conflicting resolutions") shouldBe true + } } "Compiler" should "accept exports of the same module" in { @@ -143,26 +144,6 @@ class ImportsTest extends PackageTest { outLines(1) shouldEqual "42" } - "Compiler" should "reject qualified exports of the same module with conflicting hidden names" in { - the[InterpreterException] thrownBy evalTestProject( - "Test_Multiple_Conflicting_Exports_1" - ) should have message "Compilation aborted due to errors." - val outLines = consumeOut - outLines( - 1 - ) shouldEqual "Hidden 'foo' name of the exported module local.Test_Multiple_Conflicting_Exports_1.F1 conflicts with the qualified export" - } - - "Compiler" should "reject unqualified exports of the same module with conflicting hidden names" in { - the[InterpreterException] thrownBy evalTestProject( - "Test_Multiple_Conflicting_Exports_2" - ) should have message "Compilation aborted due to errors." - val outLines = consumeOut - outLines( - 1 - ) shouldEqual "Hidden 'bar' name of the export module local.Test_Multiple_Conflicting_Exports_2.F1 conflicts with the unqualified export" - } - "Polyglot symbols" should "not be exported" in { val ex = the[InterpreterException] thrownBy evalTestProject( "Test_Polyglot_Exports" diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java index 90863fbf8f..2d103c85c2 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java @@ -1823,32 +1823,30 @@ final class TreeToIr { @SuppressWarnings("unchecked") Export translateExport(Tree.Export exp) { try { + if (exp.getHiding() != null) { + return translateSyntaxError(exp, invalidExportReason("`hiding` not allowed in `export` statement")); + } + if (exp.getAll() != null) { + return translateSyntaxError(exp, invalidExportReason("`all` not allowed in `export` statement")); + } Option rename; if (exp.getAs() == null) { rename = Option.empty(); } else { rename = Option.apply(buildName(exp.getAs().getBody(), true)); } - Option> hidingNames; - if (exp.getHiding() == null) { - hidingNames = Option.empty(); - } else { - hidingNames = Option.apply(buildNameSequence(exp.getHiding().getBody())); - } Name.Qualified qualifiedName; Option> onlyNames = Option.empty(); if (exp.getFrom() != null) { qualifiedName = buildQualifiedName(exp.getFrom().getBody(), Option.empty(), true); var onlyBodies = exp.getExport().getBody(); - if (exp.getAll() == null) { - onlyNames = Option.apply(buildNameSequence(onlyBodies)); - } + onlyNames = Option.apply(buildNameSequence(onlyBodies)); } else { qualifiedName = buildQualifiedName(exp.getExport().getBody(), Option.empty(), true); } return new Export.Module( - qualifiedName, rename, (exp.getFrom() != null), onlyNames, - hidingNames, getIdentifiedLocation(exp), false, + qualifiedName, rename, onlyNames, + getIdentifiedLocation(exp), false, meta(), diag() ); } catch (SyntaxException err) { diff --git a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/expression/errors/ImportExport.scala b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/expression/errors/ImportExport.scala index dc0c9b1572..fa73078a75 100644 --- a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/expression/errors/ImportExport.scala +++ b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/expression/errors/ImportExport.scala @@ -165,6 +165,14 @@ object ImportExport { s"The symbol $symbolName (module, type, method, or constructor) does not exist in $moduleOrTypeName." } + case class IllegalImportFromMethod( + moduleName: String, + methodName: String + ) extends Reason { + override def message(source: (IdentifiedLocation => String)): String = + s"Cannot import symbols from method '$moduleName.$methodName'" + } + case class NoSuchConstructor( typeName: String, constructorName: String @@ -173,6 +181,32 @@ object ImportExport { s"No such constructor ${constructorName} in type $typeName" } + case class NoSuchModuleMethod( + moduleName: String, + methodName: String + ) extends Reason { + override def message(source: (IdentifiedLocation => String)): String = + s"No such method ${methodName} on module $moduleName" + } + + case class NoSuchStaticMethod( + moduleName: String, + typeName: String, + methodName: String + ) extends Reason { + override def message(source: (IdentifiedLocation => String)): String = + s"No such static method ${methodName} on type $typeName in module $moduleName" + } + + case class NoSuchConversionMethod( + moduleName: String, + targetTypeName: String, + sourceTypeName: String + ) extends Reason { + override def message(source: (IdentifiedLocation => String)): String = + s"No such conversion method from $sourceTypeName to $targetTypeName in module $moduleName" + } + case class ExportSymbolsFromPrivateModule( moduleName: String ) extends Reason { diff --git a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/expression/warnings/Shadowed.scala b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/expression/warnings/Shadowed.scala index 26aee8bd20..2978b0e026 100644 --- a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/expression/warnings/Shadowed.scala +++ b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/expression/warnings/Shadowed.scala @@ -54,7 +54,7 @@ object Shadowed { /** A warning that a submodule is being shadowed by the type of the same name * therefore preventing the user from accessing the module via a qualified name. * - * @param typename the type name shadowing the module + * @param typeName the type name shadowing the module * @param moduleName the module being shadowed * @param shadower the expression shadowing `moduleName` * @param location the location at which the shadowing takes place diff --git a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/module/scope/Export.scala b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/module/scope/Export.scala index 977566da57..0a1bb8937b 100644 --- a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/module/scope/Export.scala +++ b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/module/scope/Export.scala @@ -41,9 +41,7 @@ object Export { * * @param name the full path representing the export * @param rename the name this export is visible as - * @param isAll is this an unqualified export * @param onlyNames exported names selected from the exported module - * @param hiddenNames exported names hidden from the exported module * @param location the source location that the node corresponds to * @param isSynthetic is this export compiler-generated * @param passData the pass metadata associated with this node @@ -52,9 +50,7 @@ object Export { sealed case class Module( name: Name.Qualified, rename: Option[Name.Literal], - isAll: Boolean, onlyNames: Option[List[Name.Literal]], - hiddenNames: Option[List[Name.Literal]], override val location: Option[IdentifiedLocation], isSynthetic: Boolean = false, override val passData: MetadataStorage = new MetadataStorage(), @@ -68,9 +64,7 @@ object Export { * * @param name the full path representing the export * @param rename the name this export is visible as - * @param isAll is this an unqualified export * @param onlyNames exported names selected from the exported module - * @param hiddenNames exported names hidden from the exported module * @param location the source location that the node corresponds to * @param isSynthetic is this import compiler-generated * @param passData the pass metadata associated with this node @@ -79,23 +73,19 @@ object Export { * @return a copy of `this`, updated with the specified values */ def copy( - name: Name.Qualified = name, - rename: Option[Name.Literal] = rename, - isAll: Boolean = isAll, - onlyNames: Option[List[Name.Literal]] = onlyNames, - hiddenNames: Option[List[Name.Literal]] = hiddenNames, - location: Option[IdentifiedLocation] = location, - isSynthetic: Boolean = isSynthetic, - passData: MetadataStorage = passData, - diagnostics: DiagnosticStorage = diagnostics, - id: UUID @Identifier = id + name: Name.Qualified = name, + rename: Option[Name.Literal] = rename, + onlyNames: Option[List[Name.Literal]] = onlyNames, + location: Option[IdentifiedLocation] = location, + isSynthetic: Boolean = isSynthetic, + passData: MetadataStorage = passData, + diagnostics: DiagnosticStorage = diagnostics, + id: UUID @Identifier = id ): Module = { val res = Module( name, rename, - isAll, onlyNames, - hiddenNames, location, isSynthetic, passData, @@ -138,9 +128,7 @@ object Export { |Module.Scope.Export.Module( |name = $name, |rename = $rename, - |isAll = $isAll, |onlyNames = $onlyNames, - |hidingNames = $hiddenNames, |location = $location, |passData = ${this.showPassData}, |diagnostics = $diagnostics, @@ -152,24 +140,17 @@ object Export { override def children: List[IR] = name :: List( rename.toList, - onlyNames.getOrElse(List()), - hiddenNames.getOrElse(List()) + onlyNames.getOrElse(List()) ).flatten /** @inheritdoc */ override def showCode(indent: Int): String = { val renameCode = rename.map(n => s" as ${n.name}").getOrElse("") - if (isAll) { - val onlyPart = onlyNames - .map(names => " " + names.map(_.name).mkString(", ")) - .getOrElse("") - val hidingPart = hiddenNames - .map(names => s" hiding ${names.map(_.name).mkString(", ")}") - .getOrElse("") - val all = if (onlyNames.isDefined) "" else " all" - s"from ${name.name}$renameCode export$onlyPart$all$hidingPart" - } else { - s"export ${name.name}$renameCode" + onlyNames match { + case Some(names) => + s"from ${name.name} export ${names.map(_.name).mkString(", ")}$renameCode" + case None => + s"export ${name.name}$renameCode" } } @@ -190,11 +171,8 @@ object Export { * @return whether the name could be accessed or not */ def allowsAccess(name: String): Boolean = { - if (!isAll) return false; if (onlyNames.isDefined) { onlyNames.get.exists(_.name.toLowerCase == name.toLowerCase) - } else if (hiddenNames.isDefined) { - !hiddenNames.get.exists(_.name.toLowerCase == name.toLowerCase) } else { true } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/caches/ImportExportCache.java b/engine/runtime/src/main/java/org/enso/interpreter/caches/ImportExportCache.java index 410059232f..3a243b7e59 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/caches/ImportExportCache.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/caches/ImportExportCache.java @@ -204,29 +204,17 @@ public final class ImportExportCache @Persistable(clazz = BindingsMap.ResolvedModule.class, id = 33012) @Persistable(clazz = BindingsMap.ResolvedType.class, id = 33013) @Persistable(clazz = BindingsMap.ResolvedModuleMethod.class, id = 33014) - @Persistable(clazz = BindingsMap.ResolvedStaticMethod.class, id = 33015) + @Persistable(clazz = BindingsMap.ResolvedExtensionMethod.class, id = 33015) @Persistable(clazz = BindingsMap.ResolvedConversionMethod.class, id = 33016) @Persistable(clazz = BindingsMap.ExportedModule.class, id = 33017) - @Persistable(clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$Only.class, id = 33018) - @Persistable(clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$Union.class, id = 33019) - @Persistable( - clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$Intersect.class, - id = 33020) - @Persistable( - clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$AllowedResolution.class, - id = 33021) - @Persistable(clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$All$.class, id = 33022) - @Persistable( - clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$Hiding.class, - id = 33023) - @Persistable(clazz = BindingsMap.Resolution.class, id = 33024) - @Persistable(clazz = BindingsMap.ResolvedConstructor.class, id = 33025) - @Persistable(clazz = BindingsMap.ResolvedPolyglotSymbol.class, id = 33026) - @Persistable(clazz = BindingsMap.ResolvedPolyglotField.class, id = 33027) - @Persistable(clazz = BindingsMap.ModuleMethod.class, id = 33028) - @Persistable(clazz = BindingsMap.StaticMethod.class, id = 33029) - @Persistable(clazz = BindingsMap.ConversionMethod.class, id = 33030) - @Persistable(clazz = BindingsMap.Argument.class, id = 33031) + @Persistable(clazz = BindingsMap.Resolution.class, id = 33018) + @Persistable(clazz = BindingsMap.ResolvedConstructor.class, id = 33019) + @Persistable(clazz = BindingsMap.ResolvedPolyglotSymbol.class, id = 33020) + @Persistable(clazz = BindingsMap.ResolvedPolyglotField.class, id = 33021) + @Persistable(clazz = BindingsMap.ModuleMethod.class, id = 33022) + @Persistable(clazz = BindingsMap.ExtensionMethod.class, id = 33023) + @Persistable(clazz = BindingsMap.ConversionMethod.class, id = 33024) + @Persistable(clazz = BindingsMap.Argument.class, id = 33025) @ServiceProvider(service = Persistance.class) public static final class PersistBindingsMap extends Persistance { public PersistBindingsMap() { @@ -238,7 +226,6 @@ public final class ImportExportCache out.writeObject(obj.definedEntities()); out.writeObject(obj.currentModule()); out.writeInline(scala.collection.immutable.List.class, obj.resolvedImports()); - out.writeInline(scala.collection.immutable.List.class, obj.resolvedExports()); out.writeInline(scala.collection.immutable.Map.class, obj.exportedSymbols()); } @@ -248,11 +235,9 @@ public final class ImportExportCache var de = (scala.collection.immutable.List) in.readObject(); var cm = (ModuleReference) in.readObject(); var imp = in.readInline(scala.collection.immutable.List.class); - var exp = in.readInline(scala.collection.immutable.List.class); var sym = in.readInline(scala.collection.immutable.Map.class); var map = new BindingsMap(de, cm); map.resolvedImports_$eq(imp); - map.resolvedExports_$eq(exp); map.exportedSymbols_$eq(sym); return map; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodImportResolver.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodImportResolver.java index 2dfcc96c16..65f8ec0166 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodImportResolver.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodImportResolver.java @@ -9,12 +9,24 @@ import org.enso.editions.LibraryName; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.Module; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.data.EnsoObject; import org.enso.interpreter.runtime.data.Type; +import org.enso.interpreter.runtime.data.atom.AtomConstructor; import org.enso.interpreter.runtime.scope.TopLevelScope; final class InvokeMethodImportResolver - extends ImportResolverAlgorithm { + extends ImportResolverAlgorithm< + EnsoObject, + Module, + UnresolvedSymbol, + Object, + Type, + Module, + AtomConstructor, + Function, + Function, + Function> { private final Module module; private final TopLevelScope topScope; @@ -53,13 +65,28 @@ final class InvokeMethodImportResolver } @Override - protected List exportsFor(Module module, String impName) { - return Collections.emptyList(); + protected String nameForConstructor(AtomConstructor cons) { + return cons.getName(); } @Override - protected boolean isAll(Object ex) { - return false; + protected String nameForModuleMethod(Function function) { + return function.getName(); + } + + @Override + protected String nameForExtensionMethod(Function function) { + return function.getName(); + } + + @Override + protected String nameForConversionMethod(Function function) { + return function.getName(); + } + + @Override + protected List exportsFor(Module module, String impName) { + return Collections.emptyList(); } @Override @@ -68,13 +95,28 @@ final class InvokeMethodImportResolver } @Override - protected List hiddenNames(Object ex) { + protected List definedEntities(UnresolvedSymbol symbol) { + return module.getScope().getAllTypes(symbol.getName()); + } + + @Override + protected List definedConstructors(UnresolvedSymbol symbol) { + return Collections.emptyList(); + } + + @Override + protected List definedModuleMethods(UnresolvedSymbol symbol) { + return Collections.emptyList(); + } + + @Override + protected List definedExtensionMethods(UnresolvedSymbol imp) { return null; } @Override - protected List definedEntities(UnresolvedSymbol symbol) { - return module.getScope().getAllTypes(symbol.getName()); + protected List definedConversionMethods(UnresolvedSymbol imp) { + return null; } @Override @@ -94,6 +136,30 @@ final class InvokeMethodImportResolver return typ; } + @Override + protected EnsoObject createResolvedConstructor( + UnresolvedSymbol imp, List exp, AtomConstructor cons) { + throw new UnsupportedOperationException("unimplemented"); + } + + @Override + protected EnsoObject createResolvedModuleMethod( + UnresolvedSymbol imp, List exp, Function function) { + throw new UnsupportedOperationException("unimplemented"); + } + + @Override + protected EnsoObject createResolvedExtensionMethods( + UnresolvedSymbol imp, List exp, List functions) { + throw new UnsupportedOperationException("unimplemented"); + } + + @Override + protected EnsoObject createResolvedConversionMethods( + UnresolvedSymbol imp, List exp, List functions) { + throw new UnsupportedOperationException("unimplemented"); + } + @Override protected EnsoObject createErrorPackageCoundNotBeLoaded( UnresolvedSymbol imp, String impName, String loadingError) { 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 6149ee178d..f5de1e7839 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 @@ -171,6 +171,10 @@ public final class ModuleScope implements EnsoObject { return tpes; } + public List getAllTypes() { + return types.values().stream().collect(Collectors.toUnmodifiableList()); + } + @ExportMessage.Ignore public Type getType(String name, boolean ignoreAssociatedType) { if (!ignoreAssociatedType && associatedType.getName().equals(name)) { 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 d4b0370c2f..616334567c 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 @@ -41,11 +41,7 @@ import org.enso.compiler.core.ir.expression.{ Operator, Section } -import org.enso.compiler.data.BindingsMap.{ - ExportedModule, - ResolvedConstructor, - ResolvedModule -} +import org.enso.compiler.data.BindingsMap.{ResolvedConstructor, ResolvedModule} import org.enso.compiler.data.{BindingsMap, CompilerConfig} import org.enso.compiler.exception.BadPatternMatch import org.enso.compiler.pass.analyse.alias.Graph.{Scope => AliasScope} @@ -197,19 +193,25 @@ class IrToTruffle( "No binding analysis at the point of codegen." ) - bindingsMap.resolvedExports - .collect { case ExportedModule(ResolvedModule(module), _, _) => module } - .foreach(exp => - scopeBuilder.addExport(new ImportExportScope(exp.unsafeAsModule())) + bindingsMap.getDirectlyExportedModules.foreach { exportedMod => + val exportedRuntimeMod = exportedMod.module.module.unsafeAsModule() + scopeBuilder.addExport( + new ImportExportScope(exportedRuntimeMod) ) + } + val importDefs = module.imports val methodDefs = module.bindings.collect { case method: definition.Method.Explicit => method } bindingsMap.resolvedImports.foreach { imp => - imp.target match { - case BindingsMap.ResolvedType(_, _) => + 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() @@ -406,7 +408,7 @@ class IrToTruffle( throw new CompilerError( "Impossible module method here, should be caught by MethodDefinitions pass." ) - case _: BindingsMap.ResolvedStaticMethod => + case _: BindingsMap.ResolvedExtensionMethod => throw new CompilerError( "Impossible static method here, should be caught by MethodDefinitions pass." ) @@ -884,7 +886,7 @@ class IrToTruffle( throw new CompilerError( "Impossible module method here, should be caught by MethodDefinitions pass." ) - case _: BindingsMap.ResolvedStaticMethod => + case _: BindingsMap.ResolvedExtensionMethod => throw new CompilerError( "Impossible static method here, should be caught by MethodDefinitions pass." ) @@ -1012,7 +1014,7 @@ class IrToTruffle( name, fun ) - case BindingsMap.ResolvedStaticMethod(module, staticMethod) => + case BindingsMap.ResolvedExtensionMethod(module, staticMethod) => val actualModule = module.unsafeAsModule() val currentScope = asScope(actualModule) actualModule.getBindingsMap.resolveName( @@ -1547,7 +1549,7 @@ class IrToTruffle( ) case Some( BindingsMap.Resolution( - BindingsMap.ResolvedStaticMethod(_, _) + BindingsMap.ResolvedExtensionMethod(_, _) ) ) => throw new CompilerError( @@ -1931,7 +1933,7 @@ class IrToTruffle( throw new CompilerError( s"Impossible here, module method ${method.name} should be caught when translating application" ) - case BindingsMap.ResolvedStaticMethod(_, staticMethod) => + case BindingsMap.ResolvedExtensionMethod(_, staticMethod) => throw new CompilerError( s"Impossible here, static method ${staticMethod.name} should be caught when translating application" ) 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 5161ce2146..e715f111a0 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 @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import org.enso.compiler.context.CompilerContext.Module; import org.enso.compiler.data.BindingsMap.DefinedEntity; +import org.enso.compiler.data.BindingsMap.ResolvedImport; import org.enso.compiler.data.BindingsMap.ResolvedName; import org.graalvm.polyglot.Context; import scala.jdk.javaapi.CollectionConverters; @@ -26,12 +27,35 @@ public class ModuleUtils { return getExportedSymbols(mod); } + public static List getResolvedImports(Context ctx, String modName) { + var ensoCtx = ContextUtils.leakContext(ctx); + var mod = ensoCtx.getPackageRepository().getLoadedModule(modName).get(); + return CollectionConverters.asJava(mod.getBindingsMap().resolvedImports()); + } + public static List getDefinedEntities(Context ctx, String modName) { var ensoCtx = ContextUtils.leakContext(ctx); var mod = ensoCtx.getPackageRepository().getLoadedModule(modName).get(); return CollectionConverters.asJava(mod.getBindingsMap().definedEntities()); } + /** + * Returns the loaded module with the given name, or null if no such module exist. + * + * @param modName Fully qualified name of the module + * @return module with the given name, or null if no such module exist + */ + public static org.enso.interpreter.runtime.Module getLoadedModule(Context ctx, String modName) { + assert modName.contains(".") : "Module name must be fully qualified"; + var ensoCtx = ContextUtils.leakContext(ctx); + var loadedModuleOpt = ensoCtx.getPackageRepository().getLoadedModule(modName); + if (loadedModuleOpt.isDefined()) { + return org.enso.interpreter.runtime.Module.fromCompilerModule(loadedModuleOpt.get()); + } else { + return null; + } + } + private static Map> getExportedSymbols(Module module) { var bindings = new HashMap>(); var bindingsScala = module.getBindingsMap().exportedSymbols(); diff --git a/lib/java/test-utils/src/main/java/org/enso/test/utils/ProjectUtils.java b/lib/java/test-utils/src/main/java/org/enso/test/utils/ProjectUtils.java index 878d0b663b..f5fdfcc95a 100644 --- a/lib/java/test-utils/src/main/java/org/enso/test/utils/ProjectUtils.java +++ b/lib/java/test-utils/src/main/java/org/enso/test/utils/ProjectUtils.java @@ -2,8 +2,11 @@ package org.enso.test.utils; import java.io.File; import java.io.IOException; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.util.Set; import java.util.function.Consumer; import org.enso.pkg.QualifiedName; @@ -38,7 +41,10 @@ public class ProjectUtils { * running via {@code enso --run }. * * @param projName Name of the project - * @param modules Set of modules. Must contain `Main` module. + * @param modules Set of modules. If the main module is not present in the set, an exception will + * be thrown once you try to {@link #testProjectRun(Builder, Path, Consumer) test} the project + * run. Note that set of modules without a main module makes sense only if you intend to test + * the compilation and not running. * @param projDir A directory in which the whole project structure will be created. Must exist and * be a directory. */ @@ -59,18 +65,13 @@ prefer-local-libraries: true assert yamlPath.toFile().exists(); var srcDir = Files.createDirectory(projDir.resolve("src")); assert srcDir.toFile().exists(); - boolean mainModuleFound = false; for (var module : modules) { - var relativePath = String.join(File.pathSeparator, module.name().pathAsJava()); + var relativePath = String.join(File.separator, module.name().pathAsJava()); var modDirPath = srcDir.resolve(relativePath); Files.createDirectories(modDirPath); var modPath = modDirPath.resolve(module.name().item() + ".enso"); Files.writeString(modPath, module.code()); - if (module.name().equals(QualifiedName.fromString("Main"))) { - mainModuleFound = true; - } } - assert mainModuleFound; } /** @@ -84,7 +85,10 @@ prefer-local-libraries: true */ public static void testProjectRun( Context.Builder ctxBuilder, Path projDir, Consumer resultConsumer) { - assert projDir.toFile().exists() && projDir.toFile().isDirectory(); + if (!(projDir.toFile().exists() && projDir.toFile().isDirectory())) { + throw new IllegalArgumentException( + "Project directory " + projDir + " must already be created"); + } try (var ctx = ctxBuilder .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) @@ -93,6 +97,9 @@ prefer-local-libraries: true .build()) { var polyCtx = new PolyglotContext(ctx); var mainSrcPath = projDir.resolve("src").resolve("Main.enso"); + if (!mainSrcPath.toFile().exists()) { + throw new IllegalArgumentException("Main module not found in " + projDir); + } var mainMod = polyCtx.evalModule(mainSrcPath.toFile()); var assocMainModType = mainMod.getAssociatedType(); var mainMethod = mainMod.getMethod(assocMainModType, "main").get(); @@ -102,7 +109,7 @@ prefer-local-libraries: true } /** - * Just a wrapper for {@link ContextUtils#testProjectRun(Builder, Path, Consumer)}. + * Just a wrapper for {@link ProjectUtils#testProjectRun(Builder, Path, Consumer)}. * * @param projDir Root directory of the project. * @param resultConsumer Any action that is to be evaluated on the result of running the {@code @@ -111,4 +118,24 @@ prefer-local-libraries: true public static void testProjectRun(Path projDir, Consumer resultConsumer) { testProjectRun(ContextUtils.defaultContextBuilder(), projDir, resultConsumer); } + + /** Deletes provided directory recursively. */ + public static void deleteRecursively(Path rootDir) throws IOException { + Files.walkFileTree( + rootDir, + new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } } diff --git a/test/Base_Tests/src/Main.enso b/test/Base_Tests/src/Main.enso index 341bd98203..30ef067b10 100644 --- a/test/Base_Tests/src/Main.enso +++ b/test/Base_Tests/src/Main.enso @@ -5,7 +5,6 @@ from Standard.Test import all import project.Semantic.Any_Spec import project.Semantic.Case_Spec import project.Semantic.Conversion_Spec -import project.Semantic.Deep_Export.Spec as Deep_Export_Spec import project.Semantic.Default_Args_Spec import project.Semantic.Error_Spec import project.Semantic.Import_Loop.Spec as Import_Loop_Spec @@ -107,7 +106,6 @@ main filter=Nothing = Case_Spec.add_specs suite_builder Conversion_Spec.add_specs suite_builder Default_Args_Spec.add_specs suite_builder - Deep_Export_Spec.add_specs suite_builder Error_Spec.add_specs suite_builder Environment_Spec.add_specs suite_builder File_Spec.add_specs suite_builder diff --git a/test/Base_Tests/src/Semantic/Deep_Export/Internal_1.enso b/test/Base_Tests/src/Semantic/Deep_Export/Internal_1.enso index 6978b5430e..4503700b69 100644 --- a/test/Base_Tests/src/Semantic/Deep_Export/Internal_1.enso +++ b/test/Base_Tests/src/Semantic/Deep_Export/Internal_1.enso @@ -1,3 +1 @@ -from project.Semantic.Deep_Export.Internal_2 import const - -from project.Semantic.Deep_Export.Internal_2 export const +export project.Semantic.Deep_Export.Internal_2.const diff --git a/test/Base_Tests/src/Semantic/Deep_Export/Internal_4.enso b/test/Base_Tests/src/Semantic/Deep_Export/Internal_4.enso index 97fb8c20fc..01f5170c7b 100644 --- a/test/Base_Tests/src/Semantic/Deep_Export/Internal_4.enso +++ b/test/Base_Tests/src/Semantic/Deep_Export/Internal_4.enso @@ -1,3 +1 @@ -import project.Semantic.Deep_Export.Internal_5 - -from project.Semantic.Deep_Export.Internal_5 export const +export project.Semantic.Deep_Export.Internal_5.const 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 2bb0bdcdae..832212d22f 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 @@ -1,4 +1,4 @@ -from project.Data.Boolean import False +import project.Data.Boolean.Boolean.False @Builtin_Type type Any diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Data/Boolean.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Data/Boolean.enso index db556eec10..234a4dce63 100644 --- a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Data/Boolean.enso +++ b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Data/Boolean.enso @@ -1,4 +1,5 @@ -from project.Data.Boolean.Boolean export all +export project.Data.Boolean.Boolean.False +export project.Data.Boolean.Boolean.True @Builtin_Type type Boolean diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Error.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Error.enso index b11e7da899..305632d875 100644 --- a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Error.enso +++ b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Error.enso @@ -1,5 +1,5 @@ import project.Any.Any -from project.Data.Boolean import True +import project.Data.Boolean.Boolean.True @Builtin_Type type Error diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso index daf428aac9..247d1defdc 100644 --- a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso +++ b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso @@ -3,11 +3,14 @@ export project.Panic.Panic export project.Error.Error export project.Any.Any export project.Data.Array.Array -from project.Data.Boolean export Boolean, True, False +export project.Data.Boolean.Boolean +export project.Data.Boolean.Boolean.True +export project.Data.Boolean.Boolean.False export project.Data.Text.Text export project.Data.Time.Date.Date export project.Data.List.List -from project.Data.Numbers export Number, Integer +export project.Data.Numbers.Number +export project.Data.Numbers.Integer export project.Data.Vector.Vector export project.Function.Function export project.Polyglot.Java diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso index 9a7ba02992..7b34ca3727 100644 --- a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso +++ b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso @@ -6,8 +6,9 @@ import project.Errors.Common.Forbidden_Operation import project.Function.Function import project.Panic.Panic -from project.Data.Boolean import True -from project.Runtime.Context import Input, Output +import project.Data.Boolean.Boolean.True +import project.Runtime.Context.Input +import project.Runtime.Context.Output @Builtin_Type type Context