Module re-exports (#1095)

Co-authored-by: Ara Adkins <iamrecursion@users.noreply.github.com>
This commit is contained in:
Marcin Kostrzewa 2020-08-20 18:42:58 +02:00 committed by GitHub
parent 416a7f60b0
commit 6bbb499a18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1174 additions and 62 deletions

View File

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

View File

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

View File

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

View File

@ -32,6 +32,7 @@ class Passes(passes: Option[List[PassGroup]] = None) {
val functionBodyPasses = new PassGroup(
List(
MethodDefinitions,
ModuleThisToHere,
SectionsToBinOp,
OperatorToFunction,
LambdaShorthandToLambda,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
name: Cycle_Test
version: 0.0.1
enso-version: 0.1.1-rc5
license: ''
author: ''
maintainer: ''

View File

@ -0,0 +1,3 @@
import Cycle_Test.Sub.Imp
main = IO.println "Hello, World!"

View File

@ -0,0 +1,3 @@
import Cycle_Test.Sub.B
export Cycle_Test.Sub.B

View File

@ -0,0 +1,3 @@
import Cycle_Test.Sub.C
from Cycle_Test.Sub.C export all

View File

@ -0,0 +1,3 @@
import Cycle_Test.Sub.A
export Cycle_Test.Sub.A

View File

@ -0,0 +1,3 @@
import Cycle_Test.Sub.A
main = " xD"

View File

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

View File

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

View File

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

View File

@ -78,6 +78,7 @@ class GatherDiagnosticsTest extends CompilerTest {
)
val module = IR.Module(
List(),
List(),
List(
IR.Module.Scope.Definition.Atom(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
from Test.Deep_Export.Internal_1 import Const
my_fun = Const

View File

@ -0,0 +1,3 @@
from Test.Deep_Export.Internal_2 import Const
from Test.Deep_Export.Internal_2 export Const

View File

@ -0,0 +1,3 @@
import Test.Deep_Export.Internal_3
const = Internal_3.util.const + 5.bar

View File

@ -0,0 +1,3 @@
from Test.Deep_Export.Internal_4 import Internal_5
export Test.Deep_Export.Internal_4 as Util

View File

@ -0,0 +1,3 @@
import Test.Deep_Export.Internal_5
from Test.Deep_Export.Internal_5 export Const

View File

@ -0,0 +1,7 @@
foo = 123
bar = 345
const = here.foo + this.bar
Number.bar = this + 5

View 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

View File

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