mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 22:10:15 +03:00
Convert ImportSymbolAnalysis to mini pass
This commit is contained in:
parent
ad6504d901
commit
f811f2ef73
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ class Passes(config: CompilerConfig) {
|
||||
SectionsToBinOp.INSTANCE,
|
||||
OperatorToFunction,
|
||||
LambdaShorthandToLambda,
|
||||
ImportSymbolAnalysis,
|
||||
ImportSymbolAnalysis.INSTANCE,
|
||||
AmbiguousImportsAnalysis
|
||||
) ++ (if (config.privateCheckEnabled) {
|
||||
List(
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user