Skip to content

Commit

Permalink
Exporting non-existing symbols fails with compiler error (#7960)
Browse files Browse the repository at this point in the history
Adds a new compiler analysis pass that ensures that all the symbols exported via `export ...` or `from ... export ...` statements exist. If not, generates an IR Error.

# Important Notes
We already have such a compiler pass for the version with imports in [ImportSymbolAnalysis.scala](https://github.com/enso-org/enso/blob/develop/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/ImportSymbolAnalysis.scala)
  • Loading branch information
Akirathan authored Oct 9, 2023
1 parent 6a127c5 commit 4db6121
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 17 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -973,8 +973,9 @@
- [Always persist `TRACE` level logs to a file][7825]
- [Downloadable VSCode extension][7861]
- [New `project/status` route for reporting LS state][7801]
- [Add Enso-specific assertions][7883])
- [Add Enso-specific assertions][7883]
- [Modules can be `private`][7840]
- [Export of non-existing symbols results in error][7960]

[3227]: https://github.com/enso-org/enso/pull/3227
[3248]: https://github.com/enso-org/enso/pull/3248
Expand Down Expand Up @@ -1121,6 +1122,7 @@
[7861]: https://github.com/enso-org/enso/pull/7861
[7883]: https://github.com/enso-org/enso/pull/7883
[7840]: https://github.com/enso-org/enso/pull/7840
[7960]: https://github.com/enso-org/enso/pull/7960

# Enso 2.0.0-alpha.18 (2021-10-12)

Expand Down
1 change: 0 additions & 1 deletion distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ from project.Data.Numbers export Float, Integer, Number
from project.Data.Range.Extensions export all
from project.Data.Statistics export all hiding to_moment_statistic, wrap_java_call, calculate_correlation_statistics, calculate_spearman_rank, calculate_correlation_statistics_matrix, compute_fold, empty_value, is_valid
from project.Data.Text.Extensions export all
from project.Data.Time.Conversions export all
from project.Errors.Problem_Behavior.Problem_Behavior export all
from project.Function export all
from project.Meta.Enso_Project export enso_project
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,10 @@ object ImportExport {

case class SymbolDoesNotExist(
symbolName: String,
moduleName: String
moduleOrTypeName: String
) extends Reason {
override def message: String =
s"The symbol $symbolName (module or type) does not exist in module $moduleName."
s"The symbol $symbolName (module, type, or constructor) does not exist in $moduleOrTypeName."
}

case class NoSuchConstructor(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
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 org.enso.interpreter.util.ScalaConversions;
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
public Seq<IRPass> invalidatedPasses() {
return ScalaConversions.nil();
}

@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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,11 @@
* Inserts errors into imports/exports IRs if the above conditions are violated.
*/
public final class PrivateModuleAnalysis implements IRPass {
private static final PrivateModuleAnalysis singleton = new PrivateModuleAnalysis();
public static final PrivateModuleAnalysis INSTANCE = new PrivateModuleAnalysis();
private UUID uuid;

private PrivateModuleAnalysis() {}

public static PrivateModuleAnalysis getInstance() {
return singleton;
}

@Override
public void org$enso$compiler$pass$IRPass$_setter_$key_$eq(UUID v) {
this.uuid = v;
Expand Down
3 changes: 2 additions & 1 deletion engine/runtime/src/main/scala/org/enso/compiler/Passes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ class Passes(
LambdaShorthandToLambda,
ImportSymbolAnalysis,
AmbiguousImportsAnalysis,
PrivateModuleAnalysis.getInstance(),
PrivateModuleAnalysis.INSTANCE,
ExportSymbolAnalysis.INSTANCE,
ShadowedPatternFields,
UnreachableMatchBranches,
NestedPatternMatch,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.enso.compiler.pass.analyse.{
AliasAnalysis,
AmbiguousImportsAnalysis,
BindingAnalysis,
ExportSymbolAnalysis,
ImportSymbolAnalysis,
PrivateModuleAnalysis
}
Expand Down Expand Up @@ -61,7 +62,8 @@ class PassesTest extends CompilerTest {
LambdaShorthandToLambda,
ImportSymbolAnalysis,
AmbiguousImportsAnalysis,
PrivateModuleAnalysis.getInstance(),
PrivateModuleAnalysis.INSTANCE,
ExportSymbolAnalysis.INSTANCE,
ShadowedPatternFields,
UnreachableMatchBranches,
NestedPatternMatch,
Expand Down
Loading

0 comments on commit 4db6121

Please sign in to comment.