mirror of
https://github.com/enso-org/enso.git
synced 2024-09-11 13:15:52 +03:00
Module re-exports (#1095)
Co-authored-by: Ara Adkins <iamrecursion@users.noreply.github.com>
This commit is contained in:
parent
416a7f60b0
commit
6bbb499a18
@ -100,9 +100,15 @@ exists.
|
||||
|
||||
## Export Statement Semantics
|
||||
|
||||
An export statement always exports the name of the exported module (possibly
|
||||
renamed). Additionally, any items explicitly mentioned in a `from` export,
|
||||
become available as though they were defined in the exporting module.
|
||||
A qualified export statement only exports the name of the exported module
|
||||
(possibly renamed).
|
||||
|
||||
In a `from` export, any mentioned items become available as though they were
|
||||
defined in the exporting module.
|
||||
|
||||
Please note it is explicitly forbidden for export statements across modules
|
||||
to form a cycle. If export statements cycle is detected, a compile error will
|
||||
be reported.
|
||||
|
||||
## Project Main Module
|
||||
|
||||
|
@ -20,6 +20,7 @@ public class ModuleScope {
|
||||
private Map<String, AtomConstructor> constructors = new HashMap<>();
|
||||
private Map<AtomConstructor, Map<String, Function>> methods = new HashMap<>();
|
||||
private Set<ModuleScope> imports = new HashSet<>();
|
||||
private Set<ModuleScope> exports = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Creates a new object of this class.
|
||||
@ -158,7 +159,19 @@ public class ModuleScope {
|
||||
return definedHere;
|
||||
}
|
||||
return imports.stream()
|
||||
.map(scope -> scope.getMethodMapFor(atom).get(lowerName))
|
||||
.map(scope -> scope.getExportedMethod(atom, name))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private Function getExportedMethod(AtomConstructor atom, String name) {
|
||||
Function here = getMethodMapFor(atom).get(name);
|
||||
if (here != null) {
|
||||
return here;
|
||||
}
|
||||
return exports.stream()
|
||||
.map(scope -> scope.getMethodMapFor(atom).get(name))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
@ -173,10 +186,20 @@ public class ModuleScope {
|
||||
imports.add(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an information about the module exporting another module.
|
||||
*
|
||||
* @param scope the exported scope
|
||||
*/
|
||||
public void addExport(ModuleScope scope) {
|
||||
exports.add(scope);
|
||||
}
|
||||
|
||||
public Map<String, AtomConstructor> getConstructors() {
|
||||
return constructors;
|
||||
}
|
||||
|
||||
/** @return the raw method map held by this module */
|
||||
public Map<AtomConstructor, Map<String, Function>> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
@ -188,6 +211,7 @@ public class ModuleScope {
|
||||
|
||||
public void reset() {
|
||||
imports = new HashSet<>();
|
||||
exports = new HashSet<>();
|
||||
methods = new HashMap<>();
|
||||
constructors = new HashMap<>();
|
||||
}
|
||||
|
@ -10,7 +10,11 @@ import org.enso.compiler.core.IR.Expression
|
||||
import org.enso.compiler.exception.{CompilationAbortedException, CompilerError}
|
||||
import org.enso.compiler.pass.PassManager
|
||||
import org.enso.compiler.pass.analyse._
|
||||
import org.enso.compiler.phase.ImportResolver
|
||||
import org.enso.compiler.phase.{
|
||||
ExportCycleException,
|
||||
ExportsResolution,
|
||||
ImportResolver
|
||||
}
|
||||
import org.enso.interpreter.node.{ExpressionNode => RuntimeExpression}
|
||||
import org.enso.interpreter.runtime.Context
|
||||
import org.enso.interpreter.runtime.error.ModuleDoesNotExistException
|
||||
@ -47,7 +51,10 @@ class Compiler(val context: Context) {
|
||||
*/
|
||||
def run(source: Source, module: Module): Unit = {
|
||||
parseModule(module)
|
||||
val requiredModules = importResolver.mapImports(module)
|
||||
val importedModules = importResolver.mapImports(module)
|
||||
val requiredModules =
|
||||
try { new ExportsResolution().run(importedModules) }
|
||||
catch { case e: ExportCycleException => reportCycle(e) }
|
||||
requiredModules.foreach { module =>
|
||||
if (
|
||||
!module.getCompilationStage.isAtLeast(
|
||||
@ -315,6 +322,31 @@ class Compiler(val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private def reportCycle(exception: ExportCycleException): Nothing = {
|
||||
if (context.isStrictErrors) {
|
||||
context.getOut.println("Compiler encountered errors:")
|
||||
context.getOut.println("Export statements form a cycle:")
|
||||
exception.modules match {
|
||||
case List(mod) =>
|
||||
context.getOut.println(s" ${mod.getName} exports itself.")
|
||||
case first :: second :: rest =>
|
||||
context.getOut.println(
|
||||
s" ${first.getName} exports ${second.getName}"
|
||||
)
|
||||
rest.foreach { mod =>
|
||||
context.getOut.println(s" which exports ${mod.getName}")
|
||||
}
|
||||
context.getOut.println(
|
||||
s" which exports ${first.getName}, forming a cycle."
|
||||
)
|
||||
case _ =>
|
||||
}
|
||||
throw new CompilationAbortedException
|
||||
} else {
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports diagnostics from multiple modules.
|
||||
*
|
||||
|
@ -32,6 +32,7 @@ class Passes(passes: Option[List[PassGroup]] = None) {
|
||||
val functionBodyPasses = new PassGroup(
|
||||
List(
|
||||
MethodDefinitions,
|
||||
ModuleThisToHere,
|
||||
SectionsToBinOp,
|
||||
OperatorToFunction,
|
||||
LambdaShorthandToLambda,
|
||||
|
@ -94,14 +94,19 @@ object AstToIr {
|
||||
)
|
||||
}
|
||||
|
||||
val exports = presentBlocks.collect {
|
||||
case AST.Export.any(export) => translateExport(export)
|
||||
}
|
||||
|
||||
val nonImportBlocks = presentBlocks.filter {
|
||||
case AST.Import.any(_) => false
|
||||
case AST.JavaImport.any(_) => false
|
||||
case AST.Export.any(_) => false
|
||||
case _ => true
|
||||
}
|
||||
|
||||
val statements = nonImportBlocks.map(translateModuleSymbol)
|
||||
Module(imports, statements, getIdentifiedLocation(module))
|
||||
Module(imports, exports, statements, getIdentifiedLocation(module))
|
||||
}
|
||||
}
|
||||
|
||||
@ -852,6 +857,26 @@ object AstToIr {
|
||||
}
|
||||
}
|
||||
|
||||
/** Translates an export statement from its [[AST]] representation into
|
||||
* [[IR]].
|
||||
*
|
||||
* @param imp the export to translate
|
||||
* @return the [[IR]] representation of `imp`
|
||||
*/
|
||||
def translateExport(imp: AST.Export): Module.Scope.Export = {
|
||||
imp match {
|
||||
case AST.Export(path, rename, isAll, onlyNames, hiddenNames) =>
|
||||
IR.Module.Scope.Export(
|
||||
IR.Name.Qualified(path.map(buildName).toList, None),
|
||||
rename.map(buildName),
|
||||
isAll,
|
||||
onlyNames.map(_.map(buildName).toList),
|
||||
hiddenNames.map(_.map(buildName).toList),
|
||||
getIdentifiedLocation(imp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Translates an arbitrary invalid expression from the [[AST]] representation
|
||||
* of the program into its [[IR]] representation.
|
||||
*
|
||||
|
@ -11,6 +11,7 @@ import org.enso.compiler.pass.analyse.AliasAnalysis.Graph.{Scope => AliasScope}
|
||||
import org.enso.compiler.pass.analyse.AliasAnalysis.{Graph => AliasGraph}
|
||||
import org.enso.compiler.pass.analyse.{
|
||||
AliasAnalysis,
|
||||
BindingAnalysis,
|
||||
DataflowAnalysis,
|
||||
TailCall
|
||||
}
|
||||
@ -32,6 +33,7 @@ import org.enso.interpreter.node.callable.{
|
||||
SequenceLiteralNode
|
||||
}
|
||||
import org.enso.interpreter.node.controlflow._
|
||||
import org.enso.interpreter.node.expression.atom.QualifiedAccessorNode
|
||||
import org.enso.interpreter.node.expression.constant.{
|
||||
ConstantObjectNode,
|
||||
ConstructorNode,
|
||||
@ -54,6 +56,7 @@ import org.enso.interpreter.runtime.callable.argument.{
|
||||
ArgumentDefinition,
|
||||
CallArgument
|
||||
}
|
||||
import org.enso.interpreter.runtime.callable.atom.AtomConstructor
|
||||
import org.enso.interpreter.runtime.callable.function.{
|
||||
FunctionSchema,
|
||||
Function => RuntimeFunction
|
||||
@ -131,6 +134,14 @@ class IrToTruffle(
|
||||
* @param module the module for which code should be generated
|
||||
*/
|
||||
private def processModule(module: IR.Module): Unit = {
|
||||
generateReExportBindings(module)
|
||||
module
|
||||
.unsafeGetMetadata(
|
||||
BindingAnalysis,
|
||||
"No binding analysis at the point of codegen."
|
||||
)
|
||||
.resolvedExports
|
||||
.foreach { exp => moduleScope.addExport(exp.module.getScope) }
|
||||
val imports = module.imports
|
||||
val atomDefs = module.bindings.collect {
|
||||
case atom: IR.Module.Scope.Definition.Atom => atom
|
||||
@ -305,6 +316,65 @@ class IrToTruffle(
|
||||
expr
|
||||
}
|
||||
|
||||
private def generateReExportBindings(module: IR.Module): Unit = {
|
||||
def mkConsGetter(constructor: AtomConstructor): RuntimeFunction = {
|
||||
new RuntimeFunction(
|
||||
Truffle.getRuntime.createCallTarget(
|
||||
new QualifiedAccessorNode(language, constructor)
|
||||
),
|
||||
null,
|
||||
new FunctionSchema(
|
||||
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
|
||||
new ArgumentDefinition(
|
||||
0,
|
||||
"this",
|
||||
ArgumentDefinition.ExecutionMode.EXECUTE
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val bindingsMap = module.unsafeGetMetadata(
|
||||
BindingAnalysis,
|
||||
"No binding analysis at the point of codegen."
|
||||
)
|
||||
bindingsMap.exportedSymbols.foreach {
|
||||
case (name, List(resolution)) =>
|
||||
if (resolution.module != moduleScope.getModule) {
|
||||
resolution match {
|
||||
case BindingsMap.ResolvedConstructor(definitionModule, cons) =>
|
||||
val runtimeCons =
|
||||
definitionModule.getScope.getConstructors.get(cons.name)
|
||||
val fun = mkConsGetter(runtimeCons)
|
||||
moduleScope.registerMethod(
|
||||
moduleScope.getAssociatedType,
|
||||
name,
|
||||
fun
|
||||
)
|
||||
case BindingsMap.ResolvedModule(module) =>
|
||||
val runtimeCons =
|
||||
module.getScope.getAssociatedType
|
||||
val fun = mkConsGetter(runtimeCons)
|
||||
moduleScope.registerMethod(
|
||||
moduleScope.getAssociatedType,
|
||||
name,
|
||||
fun
|
||||
)
|
||||
case BindingsMap.ResolvedMethod(module, method) =>
|
||||
val fun = module.getScope.getMethods
|
||||
.get(module.getScope.getAssociatedType)
|
||||
.get(method.name)
|
||||
moduleScope.registerMethod(
|
||||
moduleScope.getAssociatedType,
|
||||
name,
|
||||
fun
|
||||
)
|
||||
case BindingsMap.ResolvedPolyglotSymbol(_, _) =>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// === Expression Processor =================================================
|
||||
// ==========================================================================
|
||||
|
@ -282,6 +282,7 @@ object IR {
|
||||
* executable code.
|
||||
*
|
||||
* @param imports the import statements that bring other modules into scope
|
||||
* @param exports the export statements for this module
|
||||
* @param bindings the top-level bindings for this module
|
||||
* @param location the source location that the node corresponds to
|
||||
* @param passData the pass metadata associated with this node
|
||||
@ -289,6 +290,7 @@ object IR {
|
||||
*/
|
||||
sealed case class Module(
|
||||
imports: List[Module.Scope.Import],
|
||||
exports: List[Module.Scope.Export],
|
||||
bindings: List[Module.Scope.Definition],
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
@ -300,6 +302,7 @@ object IR {
|
||||
/** Creates a copy of `this`.
|
||||
*
|
||||
* @param imports the import statements that bring other modules into scope
|
||||
* @param exports the export statements for this module
|
||||
* @param bindings the top-level bindings for this module
|
||||
* @param location the source location that the node corresponds to
|
||||
* @param passData the pass metadata associated with this node
|
||||
@ -309,13 +312,15 @@ object IR {
|
||||
*/
|
||||
def copy(
|
||||
imports: List[Module.Scope.Import] = imports,
|
||||
exports: List[Module.Scope.Export] = exports,
|
||||
bindings: List[Module.Scope.Definition] = bindings,
|
||||
location: Option[IdentifiedLocation] = location,
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
id: Identifier = id
|
||||
): Module = {
|
||||
val res = Module(imports, bindings, location, passData, diagnostics)
|
||||
val res =
|
||||
Module(imports, exports, bindings, location, passData, diagnostics)
|
||||
res.id = id
|
||||
res
|
||||
}
|
||||
@ -345,16 +350,18 @@ object IR {
|
||||
override def mapExpressions(fn: Expression => Expression): Module = {
|
||||
copy(
|
||||
imports = imports.map(_.mapExpressions(fn)),
|
||||
exports = exports.map(_.mapExpressions(fn)),
|
||||
bindings = bindings.map(_.mapExpressions(fn))
|
||||
)
|
||||
}
|
||||
|
||||
override def children: List[IR] = imports ++ bindings
|
||||
override def children: List[IR] = imports ++ exports ++ bindings
|
||||
|
||||
override def toString: String =
|
||||
s"""
|
||||
|IR.Module(
|
||||
|imports = $imports,
|
||||
|exports = $exports,
|
||||
|bindings = $bindings,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
@ -366,17 +373,14 @@ object IR {
|
||||
override def showCode(indent: Int): String = {
|
||||
val importsString = imports.map(_.showCode(indent)).mkString("\n")
|
||||
|
||||
val exportsString = exports.map(_.showCode(indent)).mkString("\n")
|
||||
|
||||
val defsString = bindings.map(_.showCode(indent)).mkString("\n\n")
|
||||
|
||||
if (bindings.nonEmpty && imports.nonEmpty) {
|
||||
s"$importsString\n\n$defsString"
|
||||
} else if (bindings.nonEmpty) {
|
||||
s"$defsString"
|
||||
} else {
|
||||
s"$importsString"
|
||||
}
|
||||
List(importsString, exportsString, defsString).mkString("\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
object Module {
|
||||
|
||||
/** A representation of constructs that can only occur in the top-level
|
||||
@ -393,6 +397,159 @@ object IR {
|
||||
}
|
||||
object Scope {
|
||||
|
||||
/** An export statement.
|
||||
*
|
||||
* @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 passData the pass metadata associated with this node
|
||||
* @param diagnostics compiler diagnostics for this node
|
||||
*/
|
||||
sealed case class Export(
|
||||
name: IR.Name.Qualified,
|
||||
rename: Option[IR.Name.Literal],
|
||||
isAll: Boolean,
|
||||
onlyNames: Option[List[IR.Name.Literal]],
|
||||
hiddenNames: Option[List[IR.Name.Literal]],
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
) extends IR
|
||||
with IRKind.Primitive {
|
||||
override protected var id: Identifier = randomId
|
||||
|
||||
/** Creates a copy of `this`.
|
||||
*
|
||||
* @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 passData the pass metadata associated with this node
|
||||
* @param diagnostics compiler diagnostics for this node
|
||||
* @param id the identifier for the new node
|
||||
* @return a copy of `this`, updated with the specified values
|
||||
*/
|
||||
def copy(
|
||||
name: IR.Name.Qualified = name,
|
||||
rename: Option[IR.Name.Literal] = rename,
|
||||
isAll: Boolean = isAll,
|
||||
onlyNames: Option[List[IR.Name.Literal]] = onlyNames,
|
||||
hiddenNames: Option[List[IR.Name.Literal]] = hiddenNames,
|
||||
location: Option[IdentifiedLocation] = location,
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
id: Identifier = id
|
||||
): Export = {
|
||||
val res = Export(
|
||||
name,
|
||||
rename,
|
||||
isAll,
|
||||
onlyNames,
|
||||
hiddenNames,
|
||||
location,
|
||||
passData,
|
||||
diagnostics
|
||||
)
|
||||
res.id = id
|
||||
res
|
||||
}
|
||||
|
||||
override def duplicate(
|
||||
keepLocations: Boolean = true,
|
||||
keepMetadata: Boolean = true,
|
||||
keepDiagnostics: Boolean = true
|
||||
): Export =
|
||||
copy(
|
||||
location = if (keepLocations) location else None,
|
||||
passData =
|
||||
if (keepMetadata) passData.duplicate else MetadataStorage(),
|
||||
diagnostics =
|
||||
if (keepDiagnostics) diagnostics.copy else DiagnosticStorage(),
|
||||
id = randomId
|
||||
)
|
||||
|
||||
override def setLocation(
|
||||
location: Option[IdentifiedLocation]
|
||||
): Export =
|
||||
copy(location = location)
|
||||
|
||||
override def mapExpressions(
|
||||
fn: Expression => Expression
|
||||
): Export = this
|
||||
|
||||
override def toString: String =
|
||||
s"""
|
||||
|IR.Module.Scope.Export(
|
||||
|name = $name,
|
||||
|rename = $rename,
|
||||
|isAll = $isAll,
|
||||
|onlyNames = $onlyNames,
|
||||
|hidingNames = $hiddenNames,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] =
|
||||
name :: List(
|
||||
rename.toList,
|
||||
onlyNames.getOrElse(List()),
|
||||
hiddenNames.getOrElse(List())
|
||||
).flatten
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the module visible in the importing scope,
|
||||
* either the original name or the rename.
|
||||
*
|
||||
* @return the name of this export visible in code
|
||||
*/
|
||||
def getSimpleName: IR.Name = rename.getOrElse(name.parts.last)
|
||||
|
||||
/**
|
||||
* Checks whether the export statement allows use of the given
|
||||
* exported name.
|
||||
*
|
||||
* Note that it does not verify if the name is actually exported
|
||||
* by the module, only checks if it is syntactically allowed.
|
||||
*
|
||||
* @param name the name to check
|
||||
* @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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Module-level import statements. */
|
||||
sealed trait Import extends Scope {
|
||||
override def mapExpressions(fn: Expression => Expression): Import
|
||||
|
@ -30,6 +30,10 @@ case class BindingsMap(
|
||||
*/
|
||||
var resolvedImports: List[ResolvedImport] = List()
|
||||
|
||||
var resolvedExports: List[ExportedModule] = List()
|
||||
|
||||
var exportedSymbols: Map[String, List[ResolvedName]] = Map()
|
||||
|
||||
private def findConstructorCandidates(
|
||||
name: String
|
||||
): List[ResolvedConstructor] = {
|
||||
@ -150,14 +154,10 @@ case class BindingsMap(
|
||||
Left(ResolutionNotFound)
|
||||
}
|
||||
|
||||
private def findExportedSymbolsFor(name: String): List[ResolvedName] = {
|
||||
val matchingConses = types
|
||||
.filter(_.name.toLowerCase == name.toLowerCase)
|
||||
.map(ResolvedConstructor(currentModule, _))
|
||||
val matchingMethods = moduleMethods
|
||||
.filter(_.name.toLowerCase == name.toLowerCase)
|
||||
.map(ResolvedMethod(currentModule, _))
|
||||
matchingConses ++ matchingMethods
|
||||
private def findExportedSymbolsFor(
|
||||
name: String
|
||||
): List[ResolvedName] = {
|
||||
exportedSymbols.getOrElse(name.toLowerCase, List())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,18 +171,217 @@ case class BindingsMap(
|
||||
): Either[ResolutionError, ResolvedName] = {
|
||||
handleAmbiguity(findExportedSymbolsFor(name))
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the export statements from this module into a structure ready for
|
||||
* further analysis.
|
||||
*
|
||||
* @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(_, Some(exp), mod) =>
|
||||
val restriction = if (exp.isAll) {
|
||||
if (exp.onlyNames.isDefined) {
|
||||
SymbolRestriction.Only(
|
||||
exp.onlyNames.get.map(_.name.toLowerCase).toSet
|
||||
)
|
||||
} else if (exp.hiddenNames.isDefined) {
|
||||
SymbolRestriction.Hiding(
|
||||
exp.hiddenNames.get.map(_.name.toLowerCase).toSet
|
||||
)
|
||||
} else {
|
||||
SymbolRestriction.All
|
||||
}
|
||||
} else {
|
||||
SymbolRestriction.Only(Set(exp.getSimpleName.name.toLowerCase))
|
||||
}
|
||||
val rename = if (!exp.isAll) {
|
||||
Some(exp.getSimpleName.name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
ExportedModule(mod, rename, restriction)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
* @return whether access to the symbol is permitted by this restriction.
|
||||
*/
|
||||
def canAccess(symbol: String): 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
|
||||
}
|
||||
|
||||
case object SymbolRestriction {
|
||||
|
||||
/**
|
||||
* A restriction representing a set of allowed symbols.
|
||||
*
|
||||
* @param symbols the allowed symbols.
|
||||
*/
|
||||
case class Only(symbols: Set[String]) extends SymbolRestriction {
|
||||
override def canAccess(symbol: String): Boolean =
|
||||
symbols.contains(symbol.toLowerCase)
|
||||
override def optimize: SymbolRestriction = this
|
||||
}
|
||||
|
||||
/**
|
||||
* A restriction representing a set of excluded symbols.
|
||||
*
|
||||
* @param symbols the excluded symbols.
|
||||
*/
|
||||
case class Hiding(symbols: Set[String]) extends SymbolRestriction {
|
||||
override def canAccess(symbol: String): Boolean = {
|
||||
!symbols.contains(symbol.toLowerCase)
|
||||
}
|
||||
override def optimize: SymbolRestriction = this
|
||||
}
|
||||
|
||||
/**
|
||||
* A restriction meaning there's no restriction at all.
|
||||
*/
|
||||
case object All extends SymbolRestriction {
|
||||
override def canAccess(symbol: String): Boolean = true
|
||||
override def optimize: SymbolRestriction = this
|
||||
}
|
||||
|
||||
/**
|
||||
* A complete restriction – no symbols are permitted
|
||||
*/
|
||||
case object Empty extends SymbolRestriction {
|
||||
override def canAccess(symbol: String): Boolean = false
|
||||
override def optimize: SymbolRestriction = 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 {
|
||||
override def canAccess(symbol: String): Boolean = {
|
||||
restrictions.forall(_.canAccess(symbol))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
override def canAccess(symbol: String): Boolean =
|
||||
restrictions.exists(_.canAccess(symbol))
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A representation of a resolved export statement.
|
||||
*
|
||||
* @param module the module being exported.
|
||||
* @param exportedAs the name it is exported as.
|
||||
* @param symbols any symbol restrictions connected to the export.
|
||||
*/
|
||||
case class ExportedModule(
|
||||
module: Module,
|
||||
exportedAs: Option[String],
|
||||
symbols: SymbolRestriction
|
||||
)
|
||||
|
||||
/**
|
||||
* A representation of a resolved import statement.
|
||||
*
|
||||
* @param importDef the definition of the import
|
||||
* @param exports the exports associated with the import
|
||||
* @param module the module this import resolves to
|
||||
*/
|
||||
case class ResolvedImport(
|
||||
importDef: IR.Module.Scope.Import.Module,
|
||||
exports: Option[IR.Module.Scope.Export],
|
||||
module: Module
|
||||
)
|
||||
|
||||
@ -211,15 +410,17 @@ object BindingsMap {
|
||||
/**
|
||||
* A result of successful name resolution.
|
||||
*/
|
||||
sealed trait ResolvedName
|
||||
sealed trait ResolvedName {
|
||||
def module: Module
|
||||
}
|
||||
|
||||
/**
|
||||
* A representation of a name being resolved to a constructor.
|
||||
*
|
||||
* @param definitionModule the module the constructor is defined in.
|
||||
* @param module the module the constructor is defined in.
|
||||
* @param cons a representation of the constructor.
|
||||
*/
|
||||
case class ResolvedConstructor(definitionModule: Module, cons: Cons)
|
||||
case class ResolvedConstructor(module: Module, cons: Cons)
|
||||
extends ResolvedName
|
||||
|
||||
/**
|
||||
|
@ -163,6 +163,7 @@ case object DocumentationComments extends IRPass {
|
||||
case expr: IR.Expression => resolveExpression(expr)
|
||||
case df: IR.Module.Scope.Definition => resolveDefinition(df)
|
||||
case imp: IR.Module.Scope.Import => imp
|
||||
case exp: IR.Module.Scope.Export => exp
|
||||
case arg: IR.CallArgument => arg
|
||||
case arg: IR.DefinitionArgument => arg
|
||||
case pat: IR.Pattern => pat
|
||||
|
@ -0,0 +1,80 @@
|
||||
package org.enso.compiler.pass.resolve
|
||||
|
||||
import org.enso.compiler.context.{InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.data.BindingsMap
|
||||
import org.enso.compiler.data.BindingsMap.ResolvedModule
|
||||
import org.enso.compiler.pass.IRPass
|
||||
import org.enso.compiler.pass.analyse.AliasAnalysis
|
||||
|
||||
/**
|
||||
* Performs a substitution of `this` to `here` in methods defined on the
|
||||
* current module.
|
||||
*
|
||||
* In module-level methods, both names are semantically equivalent, but `here`
|
||||
* lends itself to static resolution in later passes and thus better
|
||||
* performance characteristics.
|
||||
*
|
||||
* It also allows the runtime to never pass a valid `this` to such a method
|
||||
* (as it is guaranteed to not be used) and therefore perform optimizations,
|
||||
* e.g. in method-reexports.
|
||||
*/
|
||||
case object ModuleThisToHere extends IRPass {
|
||||
|
||||
/** The type of the metadata object that the pass writes to the IR. */
|
||||
override type Metadata = IRPass.Metadata.Empty
|
||||
|
||||
/** The type of configuration for the pass. */
|
||||
override type Config = IRPass.Configuration.Default
|
||||
|
||||
/** The passes that this pass depends _directly_ on to run. */
|
||||
override val precursorPasses: Seq[IRPass] = Seq(MethodDefinitions)
|
||||
|
||||
/** The passes that are invalidated by running this pass. */
|
||||
override val invalidatedPasses: Seq[IRPass] = Seq(AliasAnalysis)
|
||||
|
||||
/** Executes the pass on the provided `ir`, and returns a possibly transformed
|
||||
* or annotated version of `ir`.
|
||||
*
|
||||
* @param ir the Enso IR to process
|
||||
* @param moduleContext a context object that contains the information needed
|
||||
* to process a module
|
||||
* @return `ir`, possibly having made transformations or annotations to that
|
||||
* IR.
|
||||
*/
|
||||
override def runModule(
|
||||
ir: IR.Module,
|
||||
moduleContext: ModuleContext
|
||||
): IR.Module = {
|
||||
val localResolution =
|
||||
BindingsMap.Resolution(ResolvedModule(moduleContext.module))
|
||||
val newBindings = ir.bindings.map {
|
||||
case m: IR.Module.Scope.Definition.Method.Explicit =>
|
||||
if (
|
||||
m.methodReference.typePointer
|
||||
.getMetadata(MethodDefinitions)
|
||||
.contains(localResolution)
|
||||
) {
|
||||
m.copy(body = m.body.transformExpressions {
|
||||
case IR.Name.This(loc, _, _) => IR.Name.Here(loc)
|
||||
})
|
||||
} else m
|
||||
case other => other
|
||||
}
|
||||
ir.copy(bindings = newBindings)
|
||||
}
|
||||
|
||||
/** Executes the pass on the provided `ir`, and returns a possibly transformed
|
||||
* or annotated version of `ir` in an inline context.
|
||||
*
|
||||
* @param ir the Enso IR to process
|
||||
* @param inlineContext a context object that contains the information needed
|
||||
* for inline evaluation
|
||||
* @return `ir`, possibly having made transformations or annotations to that
|
||||
* IR.
|
||||
*/
|
||||
override def runExpression(
|
||||
ir: IR.Expression,
|
||||
inlineContext: InlineContext
|
||||
): IR.Expression = ir
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
package org.enso.compiler.phase
|
||||
|
||||
import org.enso.compiler.data.BindingsMap
|
||||
import org.enso.compiler.data.BindingsMap.{
|
||||
ExportedModule,
|
||||
ResolvedConstructor,
|
||||
ResolvedMethod,
|
||||
ResolvedModule,
|
||||
ResolvedPolyglotSymbol,
|
||||
SymbolRestriction
|
||||
}
|
||||
import org.enso.compiler.pass.analyse.BindingAnalysis
|
||||
import org.enso.interpreter.runtime.Module
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
/**
|
||||
* An exception signaling a loop in the export statements.
|
||||
* @param modules the modules forming the cycle.
|
||||
*/
|
||||
case class ExportCycleException(modules: List[Module])
|
||||
extends Exception(
|
||||
"Compilation aborted due to a cycle in export statements."
|
||||
)
|
||||
|
||||
class ExportsResolution {
|
||||
|
||||
private case class Edge(
|
||||
exporter: Node,
|
||||
symbols: SymbolRestriction,
|
||||
exportsAs: Option[String],
|
||||
exportee: Node
|
||||
)
|
||||
|
||||
private case class Node(
|
||||
module: Module
|
||||
) {
|
||||
var exports: List[Edge] = List()
|
||||
var exportedBy: List[Edge] = List()
|
||||
}
|
||||
|
||||
private def getBindings(module: Module): BindingsMap =
|
||||
module.getIr.unsafeGetMetadata(
|
||||
BindingAnalysis,
|
||||
"module without binding analysis in Exports Resolution"
|
||||
)
|
||||
|
||||
private def buildGraph(modules: List[Module]): List[Node] = {
|
||||
val nodes = mutable.Map[Module, Node](
|
||||
modules.map(mod => (mod, Node(mod))): _*
|
||||
)
|
||||
modules.foreach { module =>
|
||||
val exports = getBindings(module).getDirectlyExportedModules
|
||||
val node = nodes(module)
|
||||
node.exports = exports.map {
|
||||
case ExportedModule(mod, rename, restriction) =>
|
||||
Edge(node, restriction, rename, nodes(mod))
|
||||
}
|
||||
node.exports.foreach { edge => edge.exportee.exportedBy ::= edge }
|
||||
}
|
||||
nodes.values.toList
|
||||
}
|
||||
|
||||
private def findCycles(nodes: List[Node]): List[List[Node]] = {
|
||||
val visited: mutable.Set[Node] = mutable.Set()
|
||||
val inProgress: mutable.Set[Node] = mutable.Set()
|
||||
var foundCycles: List[List[Node]] = List()
|
||||
def go(node: Node): Option[(Node, List[Node])] = {
|
||||
if (inProgress.contains(node)) {
|
||||
Some((node, List()))
|
||||
} else if (visited.contains(node)) {
|
||||
None
|
||||
} else {
|
||||
inProgress.add(node)
|
||||
val children = node.exports.map(_.exportee)
|
||||
val childrenResults = children.flatMap(go)
|
||||
inProgress.remove(node)
|
||||
visited.add(node)
|
||||
childrenResults match {
|
||||
case List() => None
|
||||
case (mod, path) :: _ =>
|
||||
if (mod == node) {
|
||||
foundCycles = (mod :: path) :: foundCycles
|
||||
None
|
||||
} else {
|
||||
Some((mod, node :: path))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
nodes.foreach(go)
|
||||
foundCycles
|
||||
}
|
||||
|
||||
private def topsort(nodes: List[Node]): List[Node] = {
|
||||
val degrees = mutable.Map[Node, Int]()
|
||||
var result: List[Node] = List()
|
||||
nodes.foreach { node =>
|
||||
degrees(node) = node.exports.length
|
||||
}
|
||||
while (degrees.nonEmpty) {
|
||||
val q = mutable.Queue[Node]()
|
||||
val entry = degrees.find { case (_, deg) => deg == 0 }.get._1
|
||||
q.enqueue(entry)
|
||||
while (q.nonEmpty) {
|
||||
val item = q.dequeue()
|
||||
degrees -= item
|
||||
item.exportedBy.foreach { edge =>
|
||||
degrees(edge.exporter) -= 1
|
||||
if (degrees(edge.exporter) == 0) {
|
||||
q.enqueue(edge.exporter)
|
||||
}
|
||||
}
|
||||
result ::= item
|
||||
}
|
||||
}
|
||||
result.reverse
|
||||
}
|
||||
|
||||
private def resolveExports(nodes: List[Node]): Unit = {
|
||||
val exports = mutable.Map[Module, 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(_.module)
|
||||
.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) =>
|
||||
getBindings(module).resolvedExports =
|
||||
exports.map(ex => ex.copy(symbols = ex.symbols.optimize))
|
||||
}
|
||||
}
|
||||
|
||||
private def resolveExportedSymbols(modules: List[Module]): Unit = {
|
||||
modules.foreach { module =>
|
||||
val bindings = getBindings(module)
|
||||
val ownMethods = bindings.moduleMethods.map(method =>
|
||||
(method.name.toLowerCase, List(ResolvedMethod(module, method)))
|
||||
)
|
||||
val ownConstructors = bindings.types.map(tp =>
|
||||
(tp.name.toLowerCase, List(ResolvedConstructor(module, tp)))
|
||||
)
|
||||
val ownPolyglotBindings = bindings.polyglotSymbols.map(poly =>
|
||||
(poly.name.toLowerCase, List(ResolvedPolyglotSymbol(module, poly)))
|
||||
)
|
||||
val exportedModules = bindings.resolvedExports.collect {
|
||||
case ExportedModule(mod, Some(name), _) =>
|
||||
(name.toLowerCase, List(ResolvedModule(mod)))
|
||||
}
|
||||
val reExportedSymbols = bindings.resolvedExports.flatMap { export =>
|
||||
getBindings(export.module).exportedSymbols.toList.filter {
|
||||
case (sym, _) => export.symbols.canAccess(sym)
|
||||
}
|
||||
}
|
||||
bindings.exportedSymbols = List(
|
||||
ownMethods,
|
||||
ownConstructors,
|
||||
ownPolyglotBindings,
|
||||
exportedModules,
|
||||
reExportedSymbols
|
||||
).flatten.groupBy(_._1).map {
|
||||
case (m, names) => (m, names.flatMap(_._2).distinct)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs exports resolution on a selected set of modules.
|
||||
*
|
||||
* The exports graph is validated and stored in the individual modules,
|
||||
* allowing further use.
|
||||
*
|
||||
* The method returns a list containing the original modules, in
|
||||
* a topological order, such that any module exported by a given module
|
||||
* comes before it in the list.
|
||||
*
|
||||
* @param modules the modules to process.
|
||||
* @return the original modules, sorted topologically.
|
||||
* @throws ExportCycleException when the export statements form a cycle.
|
||||
*/
|
||||
@throws[ExportCycleException]
|
||||
def run(modules: List[Module]): List[Module] = {
|
||||
val graph = buildGraph(modules)
|
||||
val cycles = findCycles(graph)
|
||||
if (cycles.nonEmpty) {
|
||||
throw ExportCycleException(cycles.head.map(_.module))
|
||||
}
|
||||
val tops = topsort(graph)
|
||||
resolveExports(tops)
|
||||
val topModules = tops.map(_.module)
|
||||
resolveExportedSymbols(tops.map(_.module))
|
||||
topModules
|
||||
}
|
||||
}
|
@ -51,9 +51,11 @@ class ImportResolver(compiler: Compiler) {
|
||||
)
|
||||
val importedModules = ir.imports.flatMap {
|
||||
case imp: IR.Module.Scope.Import.Module =>
|
||||
val impName = imp.name.name
|
||||
val exp = ir.exports.find(_.name.name == impName)
|
||||
compiler
|
||||
.getModule(imp.name.name)
|
||||
.map(BindingsMap.ResolvedImport(imp, _))
|
||||
.getModule(impName)
|
||||
.map(BindingsMap.ResolvedImport(imp, exp, _))
|
||||
case _ => None
|
||||
}
|
||||
|
||||
@ -71,8 +73,10 @@ class ImportResolver(compiler: Compiler) {
|
||||
None,
|
||||
None
|
||||
),
|
||||
None,
|
||||
compiler.context.getBuiltins.getModule
|
||||
)
|
||||
|
||||
currentLocal.resolvedImports =
|
||||
builtinResolution :: importedModules
|
||||
current.unsafeSetCompilationStage(
|
||||
|
@ -4,6 +4,7 @@ import org.enso.compiler.core.IR
|
||||
import org.enso.interpreter.runtime.Module
|
||||
import org.enso.compiler.core.ir.MetadataStorage._
|
||||
import org.enso.compiler.data.BindingsMap
|
||||
import org.enso.compiler.data.BindingsMap.{ResolvedConstructor, ResolvedMethod}
|
||||
import org.enso.compiler.pass.analyse.BindingAnalysis
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
@ -21,7 +22,7 @@ object StubIrBuilder {
|
||||
* @return the built stub IR.
|
||||
*/
|
||||
def build(module: Module): IR.Module = {
|
||||
val ir = IR.Module(List(), List(), None)
|
||||
val ir = IR.Module(List(), List(), List(), None)
|
||||
val scope = module.getScope
|
||||
val conses = scope.getConstructors.asScala
|
||||
val consNames = conses.keys.map(_.toLowerCase()).toSet
|
||||
@ -40,12 +41,16 @@ object StubIrBuilder {
|
||||
.getOrElse(List())
|
||||
val polyglot = scope.getPolyglotSymbols.asScala.keys.toList
|
||||
.map(BindingsMap.PolyglotSymbol)
|
||||
val exportedBindings = definedConstructors.map(c =>
|
||||
(c.name.toLowerCase, List(ResolvedConstructor(module, c)))
|
||||
) ++ moduleMethods.map(m => (m.name, List(ResolvedMethod(module, m))))
|
||||
val meta = BindingsMap(
|
||||
definedConstructors,
|
||||
polyglot,
|
||||
moduleMethods,
|
||||
module
|
||||
)
|
||||
meta.exportedSymbols = exportedBindings.toMap
|
||||
ir.updateMetadata(BindingAnalysis -->> meta)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
name: Cycle_Test
|
||||
version: 0.0.1
|
||||
enso-version: 0.1.1-rc5
|
||||
license: ''
|
||||
author: ''
|
||||
maintainer: ''
|
@ -0,0 +1,3 @@
|
||||
import Cycle_Test.Sub.Imp
|
||||
|
||||
main = IO.println "Hello, World!"
|
@ -0,0 +1,3 @@
|
||||
import Cycle_Test.Sub.B
|
||||
|
||||
export Cycle_Test.Sub.B
|
@ -0,0 +1,3 @@
|
||||
import Cycle_Test.Sub.C
|
||||
|
||||
from Cycle_Test.Sub.C export all
|
@ -0,0 +1,3 @@
|
||||
import Cycle_Test.Sub.A
|
||||
|
||||
export Cycle_Test.Sub.A
|
@ -0,0 +1,3 @@
|
||||
import Cycle_Test.Sub.A
|
||||
|
||||
main = " xD"
|
@ -209,7 +209,7 @@ trait CompilerRunner {
|
||||
* @return a module containing an atom def and method def using `expr`
|
||||
*/
|
||||
def asModuleDefs: IR.Module = {
|
||||
IR.Module(List(), List(ir.asAtomDefaultArg, ir.asMethod), None)
|
||||
IR.Module(List(), List(), List(ir.asAtomDefaultArg, ir.asMethod), None)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,13 +8,7 @@ import org.enso.compiler.pass.analyse.{AliasAnalysis, BindingAnalysis}
|
||||
import org.enso.compiler.pass.desugar._
|
||||
import org.enso.compiler.pass.lint.ShadowedPatternFields
|
||||
import org.enso.compiler.pass.optimise.UnreachableMatchBranches
|
||||
import org.enso.compiler.pass.resolve.{
|
||||
DocumentationComments,
|
||||
IgnoredBindings,
|
||||
MethodDefinitions,
|
||||
TypeFunctions,
|
||||
TypeSignatures
|
||||
}
|
||||
import org.enso.compiler.pass.resolve.{DocumentationComments, IgnoredBindings, MethodDefinitions, ModuleThisToHere, TypeFunctions, TypeSignatures}
|
||||
|
||||
class PassesTest extends CompilerTest {
|
||||
|
||||
@ -52,6 +46,7 @@ class PassesTest extends CompilerTest {
|
||||
GenerateMethodBodies,
|
||||
BindingAnalysis,
|
||||
MethodDefinitions,
|
||||
ModuleThisToHere,
|
||||
SectionsToBinOp,
|
||||
OperatorToFunction,
|
||||
LambdaShorthandToLambda,
|
||||
|
@ -803,6 +803,23 @@ class AstToIrTest extends CompilerTest with Inside {
|
||||
.map(_.showCode()) shouldEqual imports
|
||||
}
|
||||
|
||||
"properly support different kinds of exports" in {
|
||||
val exports = List(
|
||||
"export Foo.Bar as Baz",
|
||||
"export Foo.Bar",
|
||||
"from Foo.Bar export Baz",
|
||||
"from Foo.Bar export baz, Spam",
|
||||
"from Foo.Bar export all",
|
||||
"from Foo.Bar as Eggs export all hiding Spam",
|
||||
"from Foo.Bar export all hiding Spam, eggs"
|
||||
)
|
||||
exports
|
||||
.mkString("\n")
|
||||
.toIrModule
|
||||
.exports
|
||||
.map(_.showCode()) shouldEqual exports
|
||||
}
|
||||
|
||||
"AST translation of erroneous constructs" should {
|
||||
"result in a syntax error when encountering " +
|
||||
"unbalanced parentheses in application" in {
|
||||
|
@ -78,6 +78,7 @@ class GatherDiagnosticsTest extends CompilerTest {
|
||||
)
|
||||
|
||||
val module = IR.Module(
|
||||
List(),
|
||||
List(),
|
||||
List(
|
||||
IR.Module.Scope.Definition.Atom(
|
||||
|
@ -0,0 +1,102 @@
|
||||
package org.enso.compiler.test.pass.resolve
|
||||
|
||||
import org.enso.compiler.Passes
|
||||
import org.enso.compiler.context.{FreshNameSupply, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.PassConfiguration.ToPair
|
||||
import org.enso.compiler.pass.analyse.AliasAnalysis
|
||||
import org.enso.compiler.pass.optimise.ApplicationSaturation
|
||||
import org.enso.compiler.pass.resolve.ModuleThisToHere
|
||||
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
|
||||
class ModuleThisToHereTest extends CompilerTest {
|
||||
|
||||
// === Test Setup ===========================================================
|
||||
|
||||
def mkModuleContext: ModuleContext =
|
||||
buildModuleContext(
|
||||
freshNameSupply = Some(new FreshNameSupply)
|
||||
)
|
||||
|
||||
val passes = new Passes
|
||||
|
||||
val precursorPasses: PassGroup =
|
||||
passes.getPrecursors(ModuleThisToHere).get
|
||||
|
||||
val passConfiguration: PassConfiguration = PassConfiguration(
|
||||
AliasAnalysis -->> AliasAnalysis.Configuration(),
|
||||
ApplicationSaturation -->> ApplicationSaturation.Configuration()
|
||||
)
|
||||
|
||||
implicit val passManager: PassManager =
|
||||
new PassManager(List(precursorPasses), passConfiguration)
|
||||
|
||||
/** Adds an extension method to analyse an Enso module.
|
||||
*
|
||||
* @param ir the ir to analyse
|
||||
*/
|
||||
implicit class AnalyseModule(ir: IR.Module) {
|
||||
|
||||
/** Performs tail call analysis on [[ir]].
|
||||
*
|
||||
* @param context the module context in which analysis takes place
|
||||
* @return [[ir]], with tail call analysis metadata attached
|
||||
*/
|
||||
def analyse(implicit context: ModuleContext) = {
|
||||
ModuleThisToHere.runModule(ir, context)
|
||||
}
|
||||
}
|
||||
|
||||
// === The Tests ============================================================
|
||||
|
||||
"This to here desugaring" should {
|
||||
implicit val ctx: ModuleContext = mkModuleContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|type Foo a b c
|
||||
|
|
||||
|Foo.method =
|
||||
| x = this * this + this
|
||||
| y = case this of
|
||||
| A -> this * here
|
||||
| z = y -> this + y
|
||||
|
|
||||
|method =
|
||||
| x = this * this + this
|
||||
| y = case this of
|
||||
| A -> this * here
|
||||
| z = y -> this + y
|
||||
|""".stripMargin.preprocessModule.analyse
|
||||
|
||||
"desugar this to here in module methods" in {
|
||||
val method2 = ir
|
||||
.bindings(2)
|
||||
.asInstanceOf[IR.Module.Scope.Definition.Method.Explicit]
|
||||
.body
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.body
|
||||
val children = method2.preorder
|
||||
val thisOccurences = children.collect { case n: IR.Name.This => n }
|
||||
val hereOccurences = children.collect { case n: IR.Name.Here => n }
|
||||
thisOccurences.length shouldEqual 0
|
||||
hereOccurences.length shouldEqual 7
|
||||
}
|
||||
|
||||
"leave occurences of this and here untouched in non-module methods" in {
|
||||
val method1 = ir
|
||||
.bindings(1)
|
||||
.asInstanceOf[IR.Module.Scope.Definition.Method.Explicit]
|
||||
.body
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.body
|
||||
val children = method1.preorder
|
||||
val thisOccurences = children.collect { case n: IR.Name.This => n }
|
||||
val hereOccurences = children.collect { case n: IR.Name.Here => n }
|
||||
thisOccurences.length shouldEqual 6
|
||||
hereOccurences.length shouldEqual 1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -3,14 +3,10 @@ package org.enso.compiler.test.pass.resolve
|
||||
import org.enso.compiler.Passes
|
||||
import org.enso.compiler.context.{FreshNameSupply, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.data.BindingsMap.{
|
||||
Cons,
|
||||
Resolution,
|
||||
ResolvedConstructor,
|
||||
ResolvedModule
|
||||
}
|
||||
import org.enso.compiler.data.BindingsMap.{Cons, Resolution, ResolvedConstructor, ResolvedModule}
|
||||
import org.enso.compiler.pass.resolve.UppercaseNames
|
||||
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
|
||||
import org.enso.compiler.phase.ExportsResolution
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
|
||||
class UppercaseNamesTest extends CompilerTest {
|
||||
@ -24,13 +20,13 @@ class UppercaseNamesTest extends CompilerTest {
|
||||
|
||||
val passes = new Passes
|
||||
|
||||
val precursorPasses: PassGroup =
|
||||
passes.getPrecursors(UppercaseNames).get
|
||||
val group1 = passes.moduleDiscoveryPasses
|
||||
val group2 = new PassGroup(passes.functionBodyPasses.passes.takeWhile(_ != UppercaseNames))
|
||||
|
||||
val passConfiguration: PassConfiguration = PassConfiguration()
|
||||
|
||||
implicit val passManager: PassManager =
|
||||
new PassManager(List(precursorPasses), passConfiguration)
|
||||
new PassManager(List(group1, group2), passConfiguration)
|
||||
|
||||
/** Adds an extension method to analyse an Enso module.
|
||||
*
|
||||
@ -69,10 +65,12 @@ class UppercaseNamesTest extends CompilerTest {
|
||||
|add_one x = x + 1
|
||||
|
|
||||
|""".stripMargin
|
||||
val preIr = code.preprocessModule
|
||||
ctx.module.unsafeSetIr(preIr)
|
||||
|
||||
val ir = preIr.analyse
|
||||
val parsed = code.toIrModule
|
||||
val moduleMapped = passManager.runPassesOnModule(parsed, ctx, group1)
|
||||
ctx.module.unsafeSetIr(moduleMapped)
|
||||
new ExportsResolution().run(List(ctx.module))
|
||||
val allPrecursors = passManager.runPassesOnModule(moduleMapped, ctx, group2)
|
||||
val ir = allPrecursors.analyse
|
||||
|
||||
val bodyExprs = ir
|
||||
.bindings(0)
|
||||
|
@ -55,4 +55,11 @@ class ImportsTest extends PackageTest {
|
||||
.head should include("The name Atom could not be found.")
|
||||
|
||||
}
|
||||
|
||||
"Exports system" should "detect cycles" in {
|
||||
the[InterpreterException] thrownBy (evalTestProject(
|
||||
"Cycle_Test"
|
||||
)) should have message "Compilation aborted due to errors."
|
||||
consumeOut should contain("Export statements form a cycle:")
|
||||
}
|
||||
}
|
||||
|
@ -346,6 +346,13 @@ object Shape extends ShapeImplicit {
|
||||
isAll: Boolean,
|
||||
onlyNames: Option[List1[AST.Ident.Cons]],
|
||||
hidingNames: Option[List1[AST.Ident.Cons]]
|
||||
) extends SpacelessAST[T]
|
||||
final case class Export[T](
|
||||
path: List1[AST.Ident.Cons],
|
||||
rename: Option[AST.Ident.Cons],
|
||||
isAll: Boolean,
|
||||
onlyNames: Option[List1[AST.Ident]],
|
||||
hidingNames: Option[List1[AST.Ident]]
|
||||
) extends SpacelessAST[T]
|
||||
final case class JavaImport[T](path: List1[AST.Ident]) extends SpacelessAST[T]
|
||||
final case class Mixfix[T](name: List1[AST.Ident], args: List1[T])
|
||||
@ -981,6 +988,17 @@ object Shape extends ShapeImplicit {
|
||||
implicit def span[T]: HasSpan[Import[T]] = _ => 0
|
||||
}
|
||||
|
||||
object Export {
|
||||
implicit def ftor: Functor[Export] = semi.functor
|
||||
implicit def fold: Foldable[Export] = semi.foldable
|
||||
implicit def repr[T]: Repr[Export[T]] =
|
||||
t => R + "export" + t.path.repr.build()
|
||||
|
||||
// FIXME: How to make it automatic for non-spaced AST?
|
||||
implicit def ozip[T]: OffsetZip[Export, T] = _.map(Index.Start -> _)
|
||||
implicit def span[T]: HasSpan[Export[T]] = _ => 0
|
||||
}
|
||||
|
||||
object JavaImport {
|
||||
implicit def ftor: Functor[JavaImport] = semi.functor
|
||||
implicit def fold: Foldable[JavaImport] = semi.foldable
|
||||
@ -1140,9 +1158,11 @@ sealed trait ShapeImplicit {
|
||||
case s: Ambiguous[T] => s.repr
|
||||
case s: Match[T] => s.repr
|
||||
// spaceless
|
||||
case s: Comment[T] => s.repr
|
||||
case s: Documented[T] => s.repr
|
||||
case s: Import[T] => s.repr
|
||||
case s: Comment[T] => s.repr
|
||||
case s: Documented[T] => s.repr
|
||||
case s: Import[T] => s.repr
|
||||
case s: Export[T] => s.repr
|
||||
|
||||
case s: JavaImport[T] => s.repr
|
||||
case s: Mixfix[T] => s.repr
|
||||
case s: Group[T] => s.repr
|
||||
@ -1180,9 +1200,11 @@ sealed trait ShapeImplicit {
|
||||
case s: Ambiguous[T] => OffsetZip[Ambiguous, T].zipWithOffset(s)
|
||||
case s: Match[T] => OffsetZip[Match, T].zipWithOffset(s)
|
||||
// spaceless
|
||||
case s: Comment[T] => OffsetZip[Comment, T].zipWithOffset(s)
|
||||
case s: Documented[T] => OffsetZip[Documented, T].zipWithOffset(s)
|
||||
case s: Import[T] => OffsetZip[Import, T].zipWithOffset(s)
|
||||
case s: Comment[T] => OffsetZip[Comment, T].zipWithOffset(s)
|
||||
case s: Documented[T] => OffsetZip[Documented, T].zipWithOffset(s)
|
||||
case s: Import[T] => OffsetZip[Import, T].zipWithOffset(s)
|
||||
case s: Export[T] => OffsetZip[Export, T].zipWithOffset(s)
|
||||
|
||||
case s: JavaImport[T] => OffsetZip[JavaImport, T].zipWithOffset(s)
|
||||
case s: Mixfix[T] => OffsetZip[Mixfix, T].zipWithOffset(s)
|
||||
case s: Group[T] => OffsetZip[Group, T].zipWithOffset(s)
|
||||
@ -1224,6 +1246,7 @@ sealed trait ShapeImplicit {
|
||||
case s: Comment[T] => s.span()
|
||||
case s: Documented[T] => s.span()
|
||||
case s: Import[T] => s.span()
|
||||
case s: Export[T] => s.span()
|
||||
case s: JavaImport[T] => s.span()
|
||||
case s: Mixfix[T] => s.span()
|
||||
case s: Group[T] => s.span()
|
||||
@ -2362,6 +2385,36 @@ object AST {
|
||||
val any = UnapplyByType[Import]
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// Export //////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type Export = ASTOf[Shape.Export]
|
||||
|
||||
object Export {
|
||||
def apply(
|
||||
path: List1[AST.Ident.Cons],
|
||||
rename: Option[AST.Ident.Cons],
|
||||
isAll: Boolean,
|
||||
onlyNames: Option[List1[AST.Ident]],
|
||||
hidingNames: Option[List1[AST.Ident]]
|
||||
): Export =
|
||||
Shape.Export[AST](path, rename, isAll, onlyNames, hidingNames)
|
||||
def unapply(t: AST): Option[
|
||||
(
|
||||
List1[AST.Ident.Cons],
|
||||
Option[AST.Ident.Cons],
|
||||
Boolean,
|
||||
Option[List1[AST.Ident]],
|
||||
Option[List1[AST.Ident]]
|
||||
)
|
||||
] =
|
||||
Unapply[Export].run(t =>
|
||||
(t.path, t.rename, t.isAll, t.onlyNames, t.hidingNames)
|
||||
)(t)
|
||||
val any = UnapplyByType[Export]
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// Java Import /////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -267,7 +267,7 @@ object Builtin {
|
||||
case _ => internalError
|
||||
}
|
||||
}
|
||||
val (qualifiedImport, itemsImport) = {
|
||||
val (qualifiedImport, itemsImport, qualifiedExport, itemsExport) = {
|
||||
val qualNamePat = Pattern.SepList(Pattern.Cons(), Opr("."))
|
||||
val rename = Var("as") :: Pattern.Cons()
|
||||
|
||||
@ -323,22 +323,77 @@ object Builtin {
|
||||
}
|
||||
}
|
||||
|
||||
val qualifiedImport = {
|
||||
val itemsExport = {
|
||||
val all: Pattern = Var("all")
|
||||
val items = Pattern.SepList(Pattern.Cons() | Pattern.Var(), Opr(","))
|
||||
val hiding = Var("hiding") :: items
|
||||
|
||||
Definition(
|
||||
Var("from") -> (qualNamePat :: rename.opt),
|
||||
Var("export") -> ((all :: hiding.opt) | items)
|
||||
) { ctx =>
|
||||
ctx.body match {
|
||||
case List(imp, itemsMatch) =>
|
||||
val (name, rename) = extractRename(imp.body)
|
||||
val (hiding, items) = itemsMatch.body match {
|
||||
case Match.Or(_, Left(hidden)) =>
|
||||
val hiddenItems = hidden.toStream
|
||||
.map(_.wrapped)
|
||||
.drop(2)
|
||||
.collect {
|
||||
case AST.Ident.Var.any(v) => v: AST.Ident
|
||||
case AST.Ident.Cons.any(c) => c: AST.Ident
|
||||
}
|
||||
(List1(hiddenItems), None)
|
||||
|
||||
case Match.Or(_, Right(imported)) =>
|
||||
val importedItems = imported.toStream
|
||||
.map(_.wrapped)
|
||||
.collect {
|
||||
case AST.Ident.Var.any(v) => v: AST.Ident
|
||||
case AST.Ident.Cons.any(c) => c: AST.Ident
|
||||
}
|
||||
(None, List1(importedItems))
|
||||
case _ => internalError
|
||||
}
|
||||
AST.Export(name, rename, true, items, hiding)
|
||||
case _ => internalError
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def qualifiedConstruct(
|
||||
constructName: String,
|
||||
constructFactory: (
|
||||
List1[AST.Ident.Cons],
|
||||
Option[AST.Ident.Cons],
|
||||
Boolean,
|
||||
Option[List1[AST.Ident.Cons]],
|
||||
Option[List1[AST.Ident.Cons]]
|
||||
) => AST
|
||||
): Definition = {
|
||||
Definition(
|
||||
Var(
|
||||
"import"
|
||||
constructName
|
||||
) -> (qualNamePat :: rename.opt)
|
||||
) { ctx =>
|
||||
ctx.body match {
|
||||
case List(s1) =>
|
||||
val (name, rename) = extractRename(s1.body)
|
||||
AST.Import(name, rename, false, None, None)
|
||||
constructFactory(name, rename, false, None, None)
|
||||
case _ => internalError
|
||||
}
|
||||
}
|
||||
}
|
||||
(qualifiedImport, itemsImport)
|
||||
(
|
||||
qualifiedConstruct("import", AST.Import.apply),
|
||||
itemsImport,
|
||||
qualifiedConstruct("export", AST.Export.apply),
|
||||
itemsExport
|
||||
)
|
||||
}
|
||||
|
||||
val privateDef = {
|
||||
Definition(Var("private") -> Pattern.Expr()) { ctx =>
|
||||
ctx.body match {
|
||||
@ -389,6 +444,8 @@ object Builtin {
|
||||
polyglotJavaImport,
|
||||
itemsImport,
|
||||
qualifiedImport,
|
||||
itemsExport,
|
||||
qualifiedExport,
|
||||
defn,
|
||||
arrow,
|
||||
foreign,
|
||||
|
3
test/Test/src/Deep_Export/Internal.enso
Normal file
3
test/Test/src/Deep_Export/Internal.enso
Normal file
@ -0,0 +1,3 @@
|
||||
from Test.Deep_Export.Internal_1 import Const
|
||||
|
||||
my_fun = Const
|
3
test/Test/src/Deep_Export/Internal_1.enso
Normal file
3
test/Test/src/Deep_Export/Internal_1.enso
Normal file
@ -0,0 +1,3 @@
|
||||
from Test.Deep_Export.Internal_2 import Const
|
||||
|
||||
from Test.Deep_Export.Internal_2 export Const
|
3
test/Test/src/Deep_Export/Internal_2.enso
Normal file
3
test/Test/src/Deep_Export/Internal_2.enso
Normal file
@ -0,0 +1,3 @@
|
||||
import Test.Deep_Export.Internal_3
|
||||
|
||||
const = Internal_3.util.const + 5.bar
|
3
test/Test/src/Deep_Export/Internal_3.enso
Normal file
3
test/Test/src/Deep_Export/Internal_3.enso
Normal file
@ -0,0 +1,3 @@
|
||||
from Test.Deep_Export.Internal_4 import Internal_5
|
||||
|
||||
export Test.Deep_Export.Internal_4 as Util
|
3
test/Test/src/Deep_Export/Internal_4.enso
Normal file
3
test/Test/src/Deep_Export/Internal_4.enso
Normal file
@ -0,0 +1,3 @@
|
||||
import Test.Deep_Export.Internal_5
|
||||
|
||||
from Test.Deep_Export.Internal_5 export Const
|
7
test/Test/src/Deep_Export/Internal_5.enso
Normal file
7
test/Test/src/Deep_Export/Internal_5.enso
Normal file
@ -0,0 +1,7 @@
|
||||
foo = 123
|
||||
|
||||
bar = 345
|
||||
|
||||
const = here.foo + this.bar
|
||||
|
||||
Number.bar = this + 5
|
8
test/Test/src/Deep_Export_Spec.enso
Normal file
8
test/Test/src/Deep_Export_Spec.enso
Normal file
@ -0,0 +1,8 @@
|
||||
import Base.Test
|
||||
import Test.Deep_Export.Internal
|
||||
|
||||
spec =
|
||||
describe "Deep Exports" <|
|
||||
it "should allow to re-export a symbol through a module hierarchy" <|
|
||||
Internal.my_fun.should_equal 478
|
||||
|
@ -2,6 +2,7 @@ import Base.Test
|
||||
import Test.List_Spec
|
||||
import Test.Number_Spec
|
||||
import Test.Import_Loop_Spec
|
||||
import Test.Deep_Export_Spec
|
||||
import Test.Names_Spec
|
||||
|
||||
main = Test.Suite.runMain <|
|
||||
@ -9,3 +10,4 @@ main = Test.Suite.runMain <|
|
||||
Number_Spec.spec
|
||||
Import_Loop_Spec.spec
|
||||
Names_Spec.spec
|
||||
Deep_Export_Spec.spec
|
||||
|
Loading…
Reference in New Issue
Block a user