Convert ImportSymbolAnalysis to mini pass

This commit is contained in:
Pavel Marek 2024-11-06 19:36:32 +01:00
parent ad6504d901
commit f811f2ef73
6 changed files with 200 additions and 232 deletions

View File

@ -0,0 +1,194 @@
package org.enso.compiler.pass.analyse;
import java.util.ArrayList;
import java.util.List;
import org.enso.common.ScalaConversions;
import org.enso.compiler.context.InlineContext;
import org.enso.compiler.context.ModuleContext;
import org.enso.compiler.core.ir.Expression;
import org.enso.compiler.core.ir.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.Import;
import org.enso.compiler.data.BindingsMap;
import org.enso.compiler.pass.IRProcessingPass;
import org.enso.compiler.pass.MiniIRPass;
import org.enso.compiler.pass.MiniPassFactory;
import org.enso.compiler.pass.desugar.GenerateMethodBodies$;
import scala.collection.immutable.Seq;
import scala.jdk.javaapi.CollectionConverters;
/**
* Performs analysis of `from ... import sym1, sym2, ...` statements - checks that all the symbols
* imported from the module can be resolved, i.e., exists. In case of unresolved symbols, replaces
* the IR import with {@link org.enso.compiler.core.ir.expression.errors.ImportExport}. Reports only
* the first unresolved symbol.
*/
public class ImportSymbolAnalysis implements MiniPassFactory {
public static final ImportSymbolAnalysis INSTANCE = new ImportSymbolAnalysis();
private ImportSymbolAnalysis() {}
@Override
public Seq<? extends IRProcessingPass> precursorPasses() {
List<IRProcessingPass> passes =
List.of(BindingAnalysis$.MODULE$, GenerateMethodBodies$.MODULE$);
return ScalaConversions.seq(passes);
}
@Override
public Seq<? extends IRProcessingPass> invalidatedPasses() {
return ScalaConversions.seq(List.of());
}
@Override
public MiniIRPass createForModuleCompilation(ModuleContext moduleContext) {
var bindingsMap = moduleContext.bindingsAnalysis();
return new Mini(bindingsMap);
}
@Override
public MiniIRPass createForInlineCompilation(InlineContext inlineContext) {
// TODO: Must not return null?
return null;
}
private static final class Mini extends MiniIRPass {
private final BindingsMap bindingsMap;
private Mini(BindingsMap bindingsMap) {
this.bindingsMap = bindingsMap;
}
@Override
public Module transformModule(Module moduleIr) {
for (var imp : CollectionConverters.asJava(moduleIr.imports())) {
var encounteredErrors = analyseSymbolsFromImport((Import.Module) imp);
if (encounteredErrors != null) {
return moduleIr.copy(
encounteredErrors,
moduleIr.exports(),
moduleIr.bindings(),
moduleIr.isPrivate(),
moduleIr.location(),
moduleIr.passData(),
moduleIr.diagnostics(),
moduleIr.id());
}
}
return moduleIr;
}
@Override
public Expression transformExpression(Expression expr) {
return expr;
}
/** Returns list of encountered errors, or null. */
private scala.collection.immutable.List<Import> analyseSymbolsFromImport(Import.Module imp) {
if (imp.onlyNames().isDefined()) {
var resolvedImport =
bindingsMap.resolvedImports().find(resImp -> resImp.importDef() == imp);
if (resolvedImport.isEmpty()) {
return null;
}
var onlyNames = imp.onlyNames().get();
var importedTargets = resolvedImport.get().targets();
var unresolvedSymbols =
importedTargets.flatMap(
importedTarget -> onlyNames.filterNot(nm -> isSymbolResolved(importedTarget, nm)));
if (unresolvedSymbols.nonEmpty()) {
return unresolvedSymbols.map(
unresolvedSym ->
createErrorForUnresolvedSymbol(imp, importedTargets.head(), unresolvedSym));
}
}
// 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.
if (imp.isAll() && !imp.isSynthetic()) {
var resolvedImport =
bindingsMap.resolvedImports().find(resImp -> resImp.importDef() == imp);
if (resolvedImport.isEmpty()) {
return null;
}
var importedTargets = resolvedImport.get().targets();
var encounteredErrors = new ArrayList<Import>();
for (var importedTarget : CollectionConverters.asJava(importedTargets)) {
switch (importedTarget) {
case BindingsMap.ResolvedModuleMethod resModMethod -> {
encounteredErrors.add(
createImportFromMethodError(
imp,
resModMethod.module().getName().toString(),
resModMethod.method().name()));
}
case BindingsMap.ResolvedExtensionMethod extMethod -> {
var staticMethod = extMethod.staticMethod();
encounteredErrors.add(
createImportFromMethodError(
imp,
extMethod.module().getName().createChild(staticMethod.tpName()).toString(),
staticMethod.methodName()));
}
case BindingsMap.ResolvedConversionMethod resConvMethod -> {
var convMethod = resConvMethod.conversionMethod();
var module = resConvMethod.module();
encounteredErrors.add(
createImportFromMethodError(
imp,
module.getName().createChild(convMethod.targetTpName()).toString(),
convMethod.methodName()));
}
default -> {}
}
}
if (!encounteredErrors.isEmpty()) {
return CollectionConverters.asScala(encounteredErrors).toList();
}
}
return null;
}
private static boolean isSymbolResolved(
BindingsMap.ImportTarget importTarget, Name.Literal symbol) {
return importTarget.findExportedSymbolsFor(symbol.name()).nonEmpty();
}
private static ImportExport createErrorForUnresolvedSymbol(
Import imp, BindingsMap.ImportTarget importTarget, Name.Literal unresolvedSymbol) {
ImportExport.Reason errorReason =
switch (importTarget) {
case BindingsMap.ResolvedModule resMod -> new ImportExport.SymbolDoesNotExist(
unresolvedSymbol.name(), resMod.module().getName().toString());
case BindingsMap.ResolvedType resType -> new ImportExport.NoSuchConstructor(
resType.tp().name(), unresolvedSymbol.name());
case BindingsMap.ResolvedConstructor resCons -> new ImportExport.NoSuchConstructor(
resCons.cons().name(), unresolvedSymbol.name());
case BindingsMap.ResolvedModuleMethod resMethod -> new ImportExport.NoSuchModuleMethod(
resMethod.method().name(), unresolvedSymbol.name());
case BindingsMap.ResolvedExtensionMethod extMethod -> new ImportExport
.NoSuchStaticMethod(
extMethod.module().getName().toString(),
extMethod.staticMethod().tpName(),
unresolvedSymbol.name());
case BindingsMap.ResolvedConversionMethod convMethod -> new ImportExport
.NoSuchConversionMethod(
convMethod.module().getName().toString(),
convMethod.conversionMethod().targetTpName(),
convMethod.conversionMethod().sourceTpName());
default -> throw new IllegalStateException("Unexpected value: " + importTarget);
};
return new ImportExport(imp, errorReason, MetadataStorage.EMPTY);
}
private static ImportExport createImportFromMethodError(
Import imp, String moduleName, String methodName) {
return new ImportExport(
imp,
new ImportExport.IllegalImportFromMethod(moduleName, methodName),
MetadataStorage.EMPTY);
}
}
}

View File

@ -37,7 +37,7 @@ public final class PrivateModuleAnalysis implements IRPass {
@Override
public Seq<IRProcessingPass> precursorPasses() {
List<IRProcessingPass> passes =
List.of(BindingAnalysis$.MODULE$, ImportSymbolAnalysis$.MODULE$);
List.of(BindingAnalysis$.MODULE$, ImportSymbolAnalysis.INSTANCE);
return CollectionConverters.asScala(passes).toList();
}

View File

@ -44,7 +44,7 @@ class Passes(config: CompilerConfig) {
SectionsToBinOp.INSTANCE,
OperatorToFunction,
LambdaShorthandToLambda,
ImportSymbolAnalysis,
ImportSymbolAnalysis.INSTANCE,
AmbiguousImportsAnalysis
) ++ (if (config.privateCheckEnabled) {
List(

View File

@ -9,7 +9,7 @@ import org.enso.compiler.core.ir.module.scope.imports
import org.enso.compiler.data.BindingsMap
import org.enso.compiler.core.CompilerError
import org.enso.compiler.data.BindingsMap.ResolvedName
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.{IRPass, IRProcessingPass}
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
@ -39,8 +39,8 @@ case object AmbiguousImportsAnalysis extends IRPass {
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] =
Seq(BindingAnalysis, ImportSymbolAnalysis)
override lazy val precursorPasses: Seq[IRProcessingPass] =
Seq(BindingAnalysis, ImportSymbolAnalysis.INSTANCE)
override lazy val invalidatedPasses: Seq[IRPass] =
Seq()

View File

@ -1,227 +0,0 @@
package org.enso.compiler.pass.analyse
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.Implicits.AsMetadata
import org.enso.compiler.core.ir.{Expression, Module, Name}
import org.enso.compiler.core.ir.module.scope.Import
import org.enso.compiler.core.ir.expression.errors
import org.enso.compiler.data.BindingsMap
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.desugar.GenerateMethodBodies
/** Performs analysis of `from ... import sym1, sym2, ...` statements - checks that all
* the symbols imported from the module can be resolved, i.e., exists.
* In case of unresolved symbols, replaces the IR import with [[errors.ImportExport]].
* Reports only the first unresolved symbol.
*/
case object ImportSymbolAnalysis extends IRPass {
override type Metadata = BindingsMap
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] =
Seq(BindingAnalysis, GenerateMethodBodies)
override lazy val invalidatedPasses: Seq[IRPass] =
Seq()
/** @inheritdoc
*/
override def runModule(
ir: Module,
moduleContext: ModuleContext
): Module = {
val bindingMap = ir.unsafeGetMetadata(
BindingAnalysis,
"BindingMap should already be present"
)
ir.copy(
imports = ir.imports.flatMap(analyseSymbolsFromImport(_, bindingMap))
)
}
/** @inheritdoc
*/
override def runExpression(
ir: Expression,
inlineContext: InlineContext
): Expression = ir
/** @return May return multiple [[errors.ImportExport]] in case of multiple unresolved symbols.
*/
private def analyseSymbolsFromImport(
imp: Import,
bindingMap: BindingsMap
): List[Import] = {
imp match {
case imp @ Import.Module(
_,
_,
_,
Some(onlyNames),
_,
_,
_,
_
) =>
bindingMap.resolvedImports.find(_.importDef == imp) match {
case Some(resolvedImport) =>
val importedTargets = resolvedImport.targets
val unresolvedSymbols = importedTargets.flatMap { importedTarget =>
onlyNames.filterNot(isSymbolResolved(importedTarget, _))
}
if (unresolvedSymbols.nonEmpty) {
unresolvedSymbols
.map(
createErrorForUnresolvedSymbol(
imp,
importedTargets.head,
_
)
)
} else {
List(imp)
}
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,
unresolvedSymbol: Name.Literal
): errors.ImportExport = {
importTarget match {
case BindingsMap.ResolvedModule(module) =>
errors.ImportExport(
imp,
errors.ImportExport.SymbolDoesNotExist(
unresolvedSymbol.name,
module.getName.toString
)
)
case BindingsMap.ResolvedType(_, tp) =>
errors.ImportExport(
imp,
errors.ImportExport.NoSuchConstructor(
tp.name,
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
)
)
}
}
private def isSymbolResolved(
importTarget: BindingsMap.ImportTarget,
symbol: Name.Literal
): Boolean = {
importTarget.findExportedSymbolsFor(symbol.name).nonEmpty
}
}

View File

@ -16,6 +16,7 @@ import scala.Option;
/** Stores metadata for the various passes. */
public final class MetadataStorage {
private Map<ProcessingPass, ProcessingPass.Metadata> metadata;
public static final MetadataStorage EMPTY = new MetadataStorage();
public MetadataStorage() {
this(Collections.emptyMap());