Replace all from ... export all with explicit exports (#10369)

Replace all exports with explicit exports.
This commit is contained in:
Pavel Marek 2024-07-11 19:34:25 +02:00 committed by GitHub
parent b189ceae47
commit 0f9852aab2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
82 changed files with 3435 additions and 1388 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -55,6 +55,16 @@ to 0, and to run `withDebug` command like this:
withDebug --debugger benchOnly -- <fully qualified benchmark name>
```
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

View File

@ -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

View File

@ -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)
<!-- /MarkdownTOC -->
## 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.

View File

@ -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);
}
}
}

View File

@ -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<IRPass> 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<IRPass> precursorPasses() {
if (precursorPasses == null) {
List<IRPass> passes = List.of(BindingAnalysis$.MODULE$, ImportSymbolAnalysis$.MODULE$);
precursorPasses = CollectionConverters.asScala(passes).toList();
}
return precursorPasses;
}
@Override
@SuppressWarnings("unchecked")
public Seq<IRPass> invalidatedPasses() {
Object obj = scala.collection.immutable.Nil$.MODULE$;
return (scala.collection.immutable.List<IRPass>) obj;
}
@Override
public Module runModule(Module moduleIr, ModuleContext moduleContext) {
List<Export> 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 extends IR> T updateMetadataInDuplicate(T sourceIr, T copyOfIr) {
return IRPass.super.updateMetadataInDuplicate(sourceIr, copyOfIr);
}
}

View File

@ -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();

View File

@ -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<Export> exportsFor(Module module, String symbol);
protected abstract boolean isAll(Export ex);
/**
* @return {@code null} or list of named imports
*/
protected abstract java.util.List<String> onlyNames(Export ex);
/**
* @return {@code null} or list of named imports
*/
protected abstract java.util.List<String> hiddenNames(Export ex);
protected abstract java.util.List<ResolvedType> definedEntities(Import name);
protected abstract java.util.List<ResolvedConstructor> definedConstructors(Import name);
protected abstract java.util.List<ResolvedModuleMethod> definedModuleMethods(Import imp);
protected abstract java.util.List<ResolvedExtensionMethod> definedExtensionMethods(Import imp);
protected abstract java.util.List<ResolvedConversionMethod> 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<Export> exp, ResolvedType m);
protected abstract Result createResolvedConstructor(
Import imp, java.util.List<Export> exp, ResolvedConstructor cons);
protected abstract Result createResolvedModuleMethod(
Import imp, java.util.List<Export> exp, ResolvedModuleMethod moduleMethod);
protected abstract Result createResolvedExtensionMethods(
Import imp,
java.util.List<Export> exp,
java.util.List<ResolvedExtensionMethod> extensionMethods);
protected abstract Result createResolvedConversionMethods(
Import imp,
java.util.List<Export> exp,
java.util.List<ResolvedConversionMethod> 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}.
*
* <p>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<ResolvedExtensionMethod> 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<ResolvedConversionMethod> 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);

View File

@ -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<Export.Module> exportsFor(Module module, String impName) {
java.util.List<Export.Module> 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<String> 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<String> 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<BindingsMap.ResolvedType> 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<ResolvedConstructor> 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<ResolvedModuleMethod> 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<ResolvedExtensionMethod> 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<ResolvedConversionMethod> 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<Import, Option<BindingsMap.ResolvedImport>> createResolvedImport(Import.Module imp, java.util.List<Export.Module> exp, CompilerContext.Module m) {
scala.Option<org.enso.compiler.data.BindingsMap.ResolvedImport> 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<Import, Option<BindingsMap.ResolvedImport>> createResolvedType(Import.Module imp, java.util.List<Export.Module> exp, BindingsMap.ResolvedType typ) {
scala.Option<org.enso.compiler.data.BindingsMap.ResolvedImport> 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<Import, Option<ResolvedImport>> createResolvedConstructor(Import.Module imp,
java.util.List<Export.Module> 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<Import, Option<ResolvedImport>> createResolvedModuleMethod(Import.Module imp,
java.util.List<Export.Module> 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<Import, Option<ResolvedImport>> createResolvedExtensionMethods(Import.Module imp,
java.util.List<Export.Module> exp, java.util.List<ResolvedExtensionMethod> extensionMethods) {
java.util.List<ImportTarget> 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<Import, Option<ResolvedImport>> createResolvedConversionMethods(
Import.Module imp, java.util.List<Export.Module> exp,
java.util.List<ResolvedConversionMethod> resolvedConversionMethods) {
java.util.List<ImportTarget> 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 <T> List<T> toScalaList(java.util.List<T> qualifiedConflicts) {
return CollectionConverters.ListHasAsScala(qualifiedConflicts).asScala().toList();
}

View File

@ -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<ResolvedName> 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);
};
}
}

View File

@ -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<Export> 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<Name> 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<ImportExport> analyseSymbolsFromModule(
CompilerContext.Module module, List<Name> 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<ImportExport>();
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<ImportExport> analyseSymbolsFromType(ResolvedType type, List<Name> symbols) {
var errors = new ArrayList<ImportExport>();
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<Diagnostic>) Seq$.MODULE$.empty());
}
}

View File

@ -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
)

View File

@ -56,7 +56,6 @@ class Passes(
)
} else List())
++ List(
ExportSymbolAnalysis.INSTANCE,
ShadowedPatternFields,
UnreachableMatchBranches,
NestedPatternMatch,

View File

@ -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}"

View File

@ -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
}

View File

@ -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))

View File

@ -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
)
)
}
}

View File

@ -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"

View File

@ -39,8 +39,6 @@ case object ModuleNameConflicts extends IRPass {
case mod @ Export.Module(
_,
_,
false,
None,
None,
None,
true,

View File

@ -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)

View File

@ -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(

View File

@ -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(

View File

@ -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

View File

@ -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(),

View File

@ -0,0 +1,8 @@
package org.enso.compiler.phase.exports
case class Edge(
exporter: Node,
symbols: List[String],
exportsAs: Option[String],
exportee: Node
)

View File

@ -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

View File

@ -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()
}

View File

@ -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);

View File

@ -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<String> 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);
}
}

View File

@ -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<String, List<BindingsMap.ResolvedName>> getExportedSymbolsFromModule(
Context ctx, String modName) {
var ensoCtx = ContextUtils.leakContext(ctx);
var mod = ensoCtx.getPackageRepository().getLoadedModule(modName).get();
return getExportedSymbols(mod);
}
private static Map<String, List<BindingsMap.ResolvedName>> getExportedSymbols(Module module) {
var bindings = new HashMap<String, List<BindingsMap.ResolvedName>>();
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;
}
}

View File

@ -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<String> 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<String> 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;
}

View File

@ -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"));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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"));
}
}

View File

@ -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)));
}
}
}

View File

@ -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<Module> runExportsResolutionSort(List<Module> 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();
}
}

View File

@ -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);

View File

@ -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)));
}
}
}

View File

@ -1 +1 @@
from project.Sub.C export all
from project.Sub.C export A

View File

@ -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

View File

@ -1,6 +0,0 @@
name: Test_Multiple_Conflicting_Exports_1
license: APLv2
enso-version: default
version: "0.0.1"
author: "Enso Team <contact@enso.org>"
maintainer: "Enso Team <contact@enso.org>"

View File

@ -1,3 +0,0 @@
export project.F1
from project.F1 export foo
from project.F1 export all hiding foo

View File

@ -1,7 +0,0 @@
from Standard.Base import IO
from project.F2 import all
main =
IO.println F1.bar
IO.println foo

View File

@ -1,6 +0,0 @@
name: Test_Multiple_Conflicting_Exports_2
license: APLv2
enso-version: default
version: "0.0.1"
author: "Enso Team <contact@enso.org>"
maintainer: "Enso Team <contact@enso.org>"

View File

@ -1,3 +0,0 @@
export project.F1
from project.F1 export all
from project.F1 export all hiding bar

View File

@ -1,7 +0,0 @@
from Standard.Base import IO
from project.F2 import all
main =
IO.println F1.bar
IO.println foo

View File

@ -1,2 +1,2 @@
export project.F1
from project.F1 export foo
export project.F1.foo

View File

@ -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

View File

@ -1 +1 @@
from project.Type_Def.Maybe export Some
export project.Type_Def.Maybe.Some

View File

@ -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,

View File

@ -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)
)
}

View File

@ -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"
)
}

View File

@ -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

View File

@ -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
}
}
}
}

View File

@ -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)),
""
)
),

View File

@ -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"

View File

@ -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<Name.Literal> rename;
if (exp.getAs() == null) {
rename = Option.empty();
} else {
rename = Option.apply(buildName(exp.getAs().getBody(), true));
}
Option<List<Name.Literal>> hidingNames;
if (exp.getHiding() == null) {
hidingNames = Option.empty();
} else {
hidingNames = Option.apply(buildNameSequence(exp.getHiding().getBody()));
}
Name.Qualified qualifiedName;
Option<List<Name.Literal>> 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) {

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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<BindingsMap> {
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<DefinedEntity>) 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;
}

View File

@ -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<EnsoObject, Module, UnresolvedSymbol, Object, Type, Module> {
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<Object> 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<Object> exportsFor(Module module, String impName) {
return Collections.emptyList();
}
@Override
@ -68,13 +95,28 @@ final class InvokeMethodImportResolver
}
@Override
protected List<String> hiddenNames(Object ex) {
protected List<Type> definedEntities(UnresolvedSymbol symbol) {
return module.getScope().getAllTypes(symbol.getName());
}
@Override
protected List<AtomConstructor> definedConstructors(UnresolvedSymbol symbol) {
return Collections.emptyList();
}
@Override
protected List<Function> definedModuleMethods(UnresolvedSymbol symbol) {
return Collections.emptyList();
}
@Override
protected List<Function> definedExtensionMethods(UnresolvedSymbol imp) {
return null;
}
@Override
protected List<Type> definedEntities(UnresolvedSymbol symbol) {
return module.getScope().getAllTypes(symbol.getName());
protected List<Function> definedConversionMethods(UnresolvedSymbol imp) {
return null;
}
@Override
@ -94,6 +136,30 @@ final class InvokeMethodImportResolver
return typ;
}
@Override
protected EnsoObject createResolvedConstructor(
UnresolvedSymbol imp, List<Object> exp, AtomConstructor cons) {
throw new UnsupportedOperationException("unimplemented");
}
@Override
protected EnsoObject createResolvedModuleMethod(
UnresolvedSymbol imp, List<Object> exp, Function function) {
throw new UnsupportedOperationException("unimplemented");
}
@Override
protected EnsoObject createResolvedExtensionMethods(
UnresolvedSymbol imp, List<Object> exp, List<Function> functions) {
throw new UnsupportedOperationException("unimplemented");
}
@Override
protected EnsoObject createResolvedConversionMethods(
UnresolvedSymbol imp, List<Object> exp, List<Function> functions) {
throw new UnsupportedOperationException("unimplemented");
}
@Override
protected EnsoObject createErrorPackageCoundNotBeLoaded(
UnresolvedSymbol imp, String impName, String loadingError) {

View File

@ -171,6 +171,10 @@ public final class ModuleScope implements EnsoObject {
return tpes;
}
public List<Type> getAllTypes() {
return types.values().stream().collect(Collectors.toUnmodifiableList());
}
@ExportMessage.Ignore
public Type getType(String name, boolean ignoreAssociatedType) {
if (!ignoreAssociatedType && associatedType.getName().equals(name)) {

View File

@ -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"
)

View File

@ -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<ResolvedImport> 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<DefinedEntity> 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<String, List<ResolvedName>> getExportedSymbols(Module module) {
var bindings = new HashMap<String, List<ResolvedName>>();
var bindingsScala = module.getBindingsMap().exportedSymbols();

View File

@ -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 <projDir>}.
*
* @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<Value> 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<Value> 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;
}
});
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,4 +1,4 @@
from project.Data.Boolean import False
import project.Data.Boolean.Boolean.False
@Builtin_Type
type Any

View File

@ -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

View File

@ -1,5 +1,5 @@
import project.Any.Any
from project.Data.Boolean import True
import project.Data.Boolean.Boolean.True
@Builtin_Type
type Error

View File

@ -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

View File

@ -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