Add compiler support for conversion methods (#1834)

This commit is contained in:
Ara Adkins 2021-07-08 12:51:42 +01:00 committed by GitHub
parent 520cd70d55
commit b71d01b507
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 791 additions and 100 deletions

View File

@ -409,9 +409,14 @@ object AstView {
varName match {
case AST.Ident.Var.any(v) =>
Some((v, expr, Some(exprVal), false))
case AST.Ident.Blank.any(v) =>
Some((v, expr, Some(exprVal), false))
case AST.App.Section
.Right(AST.Ident.Opr("~"), AST.Ident.Var.any(v)) =>
Some((v, expr, Some(exprVal), true))
case AST.App.Section
.Right(AST.Ident.Opr("~"), AST.Ident.Blank.any(v)) =>
Some((v, expr, Some(exprVal), true))
case _ => None
}
case _ => None
@ -420,9 +425,14 @@ object AstView {
varName match {
case AST.Ident.Var.any(v) =>
Some((v, expr, None, false))
case AST.Ident.Blank.any(v) =>
Some((v, expr, None, false))
case AST.App.Section
.Right(AST.Ident.Opr("~"), AST.Ident.Var.any(v)) =>
Some((v, expr, None, true))
case AST.App.Section
.Right(AST.Ident.Opr("~"), AST.Ident.Blank.any(v)) =>
Some((v, expr, None, true))
case _ => None
}
case _ => None

View File

@ -988,6 +988,11 @@ class IrToTruffle(
.error()
.compileError()
.newInstance(Text.create(err.message))
case err: Error.Redefined.Conversion =>
context.getBuiltins
.error()
.compileError()
.newInstance(Text.create(err.message))
case err: Error.Redefined.Atom =>
context.getBuiltins
.error()

View File

@ -5620,6 +5620,11 @@ object IR {
"A conversion definition must have at least one argument."
}
case object UnsupportedSourceType extends Reason {
override def explain: String =
"Arbitrary expressions are not yet supported as source types."
}
case class MissingSourceType(argName: String) extends Reason {
override def explain: String =
s"The argument `$argName` does not define a source type."
@ -5631,6 +5636,11 @@ object IR {
s"`$argName` does not."
}
case class SuspendedSourceArgument(argName: String) extends Reason {
override def explain: String =
s"The source type argument in a conversion (here $argName) cannot " +
s"be suspended."
}
}
/** A representation of an error resulting from name resolution.
@ -6260,6 +6270,103 @@ object IR {
override def showCode(indent: Int): String = "(Redefined This_Arg)"
}
/** An error representing the redefinition of a conversion in a given
* module. This is also known as a method overload.
*
* @param targetType the name of the atom the conversion was being
* redefined on
* @param sourceType the source type for the conversion
* @param location the location in the source to which this error
* corresponds
* @param passData the pass metadata for the error
* @param diagnostics any diagnostics associated with this error.
*/
sealed case class Conversion(
targetType: IR.Name,
sourceType: IR.Name,
override val location: Option[IdentifiedLocation],
override val passData: MetadataStorage = MetadataStorage(),
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
) extends Redefined
with Diagnostic.Kind.Interactive
with Module.Scope.Definition
with IRKind.Primitive {
override protected var id: Identifier = randomId
/** Creates a copy of `this`.
*
* @param targetType the name of the atom the conversion was being
* redefined on
* @param sourceType the source type for the conversion
* @param location the location in the source to which this error
* corresponds
* @param passData the pass metadata for the error
* @param diagnostics any diagnostics associated with this error.
* @param id the identifier for the node
* @return a copy of `this`, updated with the specified values
*/
def copy(
targetType: IR.Name = targetType,
sourceType: IR.Name = sourceType,
location: Option[IdentifiedLocation] = location,
passData: MetadataStorage = passData,
diagnostics: DiagnosticStorage = diagnostics,
id: Identifier = id
): Conversion = {
val res =
Conversion(targetType, sourceType, location, passData, diagnostics)
res.id = id
res
}
override def duplicate(
keepLocations: Boolean = true,
keepMetadata: Boolean = true,
keepDiagnostics: Boolean = true
): Conversion =
copy(
targetType = targetType
.duplicate(keepLocations, keepMetadata, keepDiagnostics),
sourceType = sourceType
.duplicate(keepLocations, keepMetadata, keepDiagnostics),
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]
): Conversion =
copy(location = location)
override def message: String =
s"Method overloads are not supported: ${targetType.name}.from " +
s"${sourceType.showCode()} is defined multiple times in this module."
override def mapExpressions(fn: Expression => Expression): Conversion =
this
override def toString: String =
s"""
|IR.Error.Redefined.Method(
|targetType = $targetType,
|sourceType = $sourceType,
|location = $location,
|passData = ${this.showPassData},
|diagnostics = $diagnostics,
|id = $id
|)
|""".stripMargin
override def children: List[IR] = List(targetType, sourceType)
override def showCode(indent: Int): String =
s"(Redefined (Conversion $targetType.from $sourceType))"
}
/** An error representing the redefinition of a method in a given module.
* This is also known as a method overload.
*

View File

@ -2,7 +2,6 @@ package org.enso.compiler.pass.analyse
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Module.Scope.Definition.Method
import org.enso.compiler.core.IR.Pattern
import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.exception.CompilerError
@ -136,10 +135,23 @@ case object AliasAnalysis extends IRPass {
val topLevelGraph = new Graph
ir match {
case _: Method.Conversion =>
throw new CompilerError("Conversion methods are not yet supported.")
case m @ IR.Module.Scope.Definition.Method
.Explicit(_, body, _, _, _) =>
case m: IR.Module.Scope.Definition.Method.Conversion =>
m.body match {
case _: IR.Function =>
m.copy(
body = analyseExpression(
m.body,
topLevelGraph,
topLevelGraph.rootScope,
lambdaReuseScope = true
)
).updateMetadata(this -->> Info.Scope.Root(topLevelGraph))
case _ =>
throw new CompilerError(
"The body of a method should always be a function."
)
}
case m @ IR.Module.Scope.Definition.Method.Explicit(_, body, _, _, _) =>
body match {
case _: IR.Function =>
m.copy(
@ -181,7 +193,7 @@ case object AliasAnalysis extends IRPass {
case _: IR.Name.Annotation =>
throw new CompilerError(
"Annotations should already be associated by the point of alias " +
"analysis."
"analysis."
)
case err: IR.Error => err
}
@ -342,7 +354,15 @@ case object AliasAnalysis extends IRPass {
scope: Scope
): List[IR.DefinitionArgument] = {
args.map {
case arg @ IR.DefinitionArgument.Specified(name, _, value, susp, _, _, _) =>
case arg @ IR.DefinitionArgument.Specified(
name,
_,
value,
susp,
_,
_,
_
) =>
val nameOccursInScope =
scope.hasSymbolOccurrenceAs[Occurrence.Def](name.name)
if (!nameOccursInScope) {

View File

@ -7,7 +7,14 @@ import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Module.Scope.Definition.Method
import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.desugar.{ComplexType, FunctionBinding, GenerateMethodBodies, LambdaShorthandToLambda, OperatorToFunction, SectionsToBinOp}
import org.enso.compiler.pass.desugar.{
ComplexType,
FunctionBinding,
GenerateMethodBodies,
LambdaShorthandToLambda,
OperatorToFunction,
SectionsToBinOp
}
import scala.collection.mutable
import scala.jdk.CollectionConverters._
@ -85,8 +92,10 @@ case object CachePreferenceAnalysis extends IRPass {
arguments.map(analyseDefinitionArgument(_, weights))
)
.updateMetadata(this -->> weights)
case _: Method.Conversion =>
throw new CompilerError("Conversion methods are not yet supported.")
case method: Method.Conversion =>
method
.copy(body = analyseExpression(method.body, weights))
.updateMetadata(this -->> weights)
case method @ IR.Module.Scope.Definition.Method
.Explicit(_, body, _, _, _) =>
method

View File

@ -103,8 +103,18 @@ case object DataflowAnalysis extends IRPass {
arguments = arguments.map(analyseDefinitionArgument(_, info))
)
.updateMetadata(this -->> info)
case _: Method.Conversion =>
throw new CompilerError("Conversion methods are not yet supported.")
case m: Method.Conversion =>
val bodyDep = asStatic(m.body)
val methodDep = asStatic(m)
val sourceTypeDep = asStatic(m.sourceTypeName)
info.dependents.updateAt(sourceTypeDep, Set(methodDep))
info.dependents.updateAt(bodyDep, Set(methodDep))
info.dependencies.updateAt(methodDep, Set(bodyDep, sourceTypeDep))
m.copy(
body = analyseExpression(m.body, info),
sourceTypeName = m.sourceTypeName.updateMetadata(this -->> info)
).updateMetadata(this -->> info)
case method @ IR.Module.Scope.Definition.Method
.Explicit(_, body, _, _, _) =>
val bodyDep = asStatic(body)

View File

@ -2,7 +2,6 @@ package org.enso.compiler.pass.analyse
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Module.Scope.Definition.Method
import org.enso.compiler.core.IR.Pattern
import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.exception.CompilerError
@ -83,8 +82,12 @@ case object TailCall extends IRPass {
definition: IR.Module.Scope.Definition
): IR.Module.Scope.Definition = {
definition match {
case _: Method.Conversion =>
throw new CompilerError("Conversion methods are not yet supported.")
case method: IR.Module.Scope.Definition.Method.Conversion =>
method
.copy(
body = analyseExpression(method.body, isInTailPosition = true)
)
.updateMetadata(this -->> TailPosition.Tail)
case method @ IR.Module.Scope.Definition.Method
.Explicit(_, body, _, _, _) =>
method

View File

@ -3,7 +3,6 @@ package org.enso.compiler.pass.resolve
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Case.Branch
import org.enso.compiler.core.IR.Module.Scope.Definition.Method
import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
@ -132,8 +131,11 @@ case object DocumentationComments extends IRPass {
ir: IR.Module.Scope.Definition
): IR.Module.Scope.Definition =
ir match {
case _: Method.Conversion =>
throw new CompilerError("Conversion methods are not yet supported.")
case _: IR.Module.Scope.Definition.Method.Conversion =>
throw new CompilerError(
"Conversion methods should not yet be present in the compiler " +
"pipeline."
)
case method: IR.Module.Scope.Definition.Method.Binding =>
method.copy(body = resolveExpression(method.body))
case method: IR.Module.Scope.Definition.Method.Explicit =>

View File

@ -13,8 +13,8 @@ import org.enso.compiler.pass.desugar.{
GenerateMethodBodies
}
/** Resolves the correct `this` argument type for methods definitions
* and stores the resolution in the method's metadata.
/** Resolves the correct `this` argument type for method definitions and stores
* the resolution in the method's metadata.
*/
case object MethodDefinitions extends IRPass {
@ -45,58 +45,101 @@ case object MethodDefinitions extends IRPass {
"MethodDefinitionResolution is being run before BindingResolution"
)
val newDefs = ir.bindings.map {
case method: IR.Module.Scope.Definition.Method.Explicit =>
case method: IR.Module.Scope.Definition.Method =>
val methodRef = method.methodReference
val resolvedTypeRef = methodRef.typePointer match {
case tp: IR.Name.Here =>
tp.updateMetadata(
this -->> BindingsMap.Resolution(
BindingsMap.ResolvedModule(availableSymbolsMap.currentModule)
)
)
case tp @ IR.Name.Qualified(names, _, _, _) =>
val items = names.map(_.name)
availableSymbolsMap.resolveQualifiedName(items) match {
case Left(err) =>
IR.Error.Resolution(tp, IR.Error.Resolution.ResolverError(err))
case Right(value: BindingsMap.ResolvedConstructor) =>
tp.updateMetadata(
this -->> BindingsMap.Resolution(value)
)
case Right(value: BindingsMap.ResolvedModule) =>
tp.updateMetadata(
this -->> BindingsMap.Resolution(value)
)
case Right(_: BindingsMap.ResolvedPolyglotSymbol) =>
IR.Error.Resolution(
tp,
IR.Error.Resolution.UnexpectedPolyglot(
"a method definition target"
)
)
case Right(_: BindingsMap.ResolvedMethod) =>
IR.Error.Resolution(
tp,
IR.Error.Resolution.UnexpectedMethod(
"a method definition target"
)
val resolvedTypeRef =
resolveType(methodRef.typePointer, availableSymbolsMap)
val resolvedMethodRef = methodRef.copy(typePointer = resolvedTypeRef)
method match {
case method: IR.Module.Scope.Definition.Method.Explicit =>
val resolvedMethod =
method.copy(methodReference = resolvedMethodRef)
resolvedMethod
case method: IR.Module.Scope.Definition.Method.Conversion =>
val sourceTypeExpr = method.sourceTypeName
val resolvedName: IR.Name = sourceTypeExpr match {
case name: IR.Name => resolveType(name, availableSymbolsMap)
case _ =>
IR.Error.Conversion(
sourceTypeExpr,
IR.Error.Conversion.UnsupportedSourceType
)
}
case tp: IR.Error.Resolution => tp
val resolvedMethod = method.copy(
methodReference = resolvedMethodRef,
sourceTypeName = resolvedName
)
resolvedMethod
case _ =>
throw new CompilerError(
"Unexpected kind of name for method reference"
"Unexpected method type in MethodDefinitions pass."
)
}
val resolvedMethodRef = methodRef.copy(typePointer = resolvedTypeRef)
val resolvedMethod = method.copy(methodReference = resolvedMethodRef)
resolvedMethod
case other => other
}
ir.copy(bindings = newDefs)
}
private def resolveType(
typePointer: IR.Name,
availableSymbolsMap: BindingsMap
): IR.Name = {
typePointer match {
case tp: IR.Name.Here =>
tp.updateMetadata(
this -->> BindingsMap.Resolution(
BindingsMap.ResolvedModule(availableSymbolsMap.currentModule)
)
)
case _: IR.Name.Qualified | _: IR.Name.Literal =>
val items = typePointer match {
case IR.Name.Qualified(names, _, _, _) => names.map(_.name)
case IR.Name.Literal(name, _, _, _, _, _) => List(name)
case _ =>
throw new CompilerError("Impossible to reach.")
}
availableSymbolsMap.resolveQualifiedName(items) match {
case Left(err) =>
IR.Error.Resolution(
typePointer,
IR.Error.Resolution.ResolverError(err)
)
case Right(value: BindingsMap.ResolvedConstructor) =>
typePointer.updateMetadata(
this -->> BindingsMap.Resolution(value)
)
case Right(value: BindingsMap.ResolvedModule) =>
typePointer.updateMetadata(
this -->> BindingsMap.Resolution(value)
)
case Right(_: BindingsMap.ResolvedPolyglotSymbol) =>
IR.Error.Resolution(
typePointer,
IR.Error.Resolution.UnexpectedPolyglot(
"a method definition target"
)
)
case Right(_: BindingsMap.ResolvedMethod) =>
IR.Error.Resolution(
typePointer,
IR.Error.Resolution.UnexpectedMethod(
"a method definition target"
)
)
}
case tp: IR.Error.Resolution => tp
case _ =>
throw new CompilerError(
"Unexpected kind of name for method reference"
)
}
}
/** Executes the pass on the provided `ir`, and returns a possibly transformed
* or annotated version of `ir` in an inline context.
*

View File

@ -4,6 +4,7 @@ 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.exception.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.analyse.AliasAnalysis
@ -48,15 +49,26 @@ case object ModuleThisToHere extends IRPass {
val localResolution =
BindingsMap.Resolution(ResolvedModule(moduleContext.module))
val newBindings = ir.bindings.map {
case m: IR.Module.Scope.Definition.Method.Explicit =>
case m: IR.Module.Scope.Definition.Method =>
if (
m.methodReference.typePointer
.getMetadata(MethodDefinitions)
.contains(localResolution)
) {
m.copy(body = m.body.transformExpressions {
val result = m.body.transformExpressions {
case IR.Name.This(loc, _, _) => IR.Name.Here(loc)
})
}
m match {
case m: IR.Module.Scope.Definition.Method.Explicit =>
m.copy(body = result)
case m: IR.Module.Scope.Definition.Method.Conversion =>
m.copy(body = result)
case _ =>
throw new CompilerError(
"Impossible method type during `ModuleThisToHere`."
)
}
} else m
case other => other
}

View File

@ -6,6 +6,7 @@ import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.desugar.{ComplexType, GenerateMethodBodies}
import scala.annotation.unused
import scala.collection.mutable
/** This pass performs static detection of method overloads and emits errors
* at the overload definition site if they are detected. It also checks for
@ -63,7 +64,7 @@ case object OverloadsResolution extends IRPass {
})
val methods = ir.bindings.collect {
case meth: IR.Module.Scope.Definition.Method =>
case meth: IR.Module.Scope.Definition.Method.Explicit =>
seenMethods = seenMethods + (meth.typeName.name -> Set())
meth
}
@ -81,8 +82,30 @@ case object OverloadsResolution extends IRPass {
}
})
val conversionsForType: mutable.Map[String, Set[String]] = mutable.Map()
val conversions: List[IR.Module.Scope.Definition] = ir.bindings.collect {
case m: IR.Module.Scope.Definition.Method.Conversion =>
val fromName = m.sourceTypeName.asInstanceOf[IR.Name]
conversionsForType.get(m.typeName.name) match {
case Some(elems) =>
if (elems.contains(fromName.name)) {
IR.Error.Redefined.Conversion(m.typeName, fromName, m.location)
} else {
conversionsForType.update(
m.typeName.name,
conversionsForType(m.typeName.name) + fromName.name
)
m
}
case None =>
conversionsForType.put(m.typeName.name, Set(fromName.name))
m
}
}
ir.copy(
bindings = newAtoms ::: newMethods
bindings = newAtoms ::: newMethods ::: conversions
)
}

View File

@ -102,8 +102,46 @@ case object SuspendedArguments extends IRPass {
binding: IR.Module.Scope.Definition
): IR.Module.Scope.Definition = {
binding match {
case _: Method.Conversion =>
throw new CompilerError("Conversion methods are not yet supported.")
case method: Method.Conversion =>
method.body match {
case lam @ IR.Function.Lambda(args, body, _, _, _, _) =>
method.getMetadata(TypeSignatures) match {
case Some(Signature(signature)) =>
val newArgs = computeSuspensions(args.drop(1), signature)
if (newArgs.head.suspended) {
IR.Error.Conversion(
method,
IR.Error.Conversion.SuspendedSourceArgument(
newArgs.head.name.name
)
)
} else {
method.copy(body =
lam.copy(
arguments = args.head :: newArgs,
body = resolveExpression(body)
)
)
}
case None =>
if (args(1).suspended) {
IR.Error.Conversion(
method,
IR.Error.Conversion.SuspendedSourceArgument(
args(1).name.name
)
)
} else {
method.copy(
body = lam.copy(body = resolveExpression(body))
)
}
}
case _ =>
throw new CompilerError(
"Method bodies must be lambdas at this point."
)
}
case explicit @ Method.Explicit(_, body, _, _, _) =>
body match {
case lam @ IR.Function.Lambda(args, lamBody, _, _, _, _) =>

View File

@ -12,6 +12,8 @@ import org.enso.compiler.pass.analyse.AliasAnalysis.{Graph, Info}
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
import scala.annotation.unused
class AliasAnalysisTest extends CompilerTest {
// === Utilities ============================================================
@ -833,6 +835,122 @@ class AliasAnalysisTest extends CompilerTest {
}
}
"Alias analysis on conversion methods" should {
implicit val ctx: ModuleContext = mkModuleContext
val conversionMethod =
"""Bar.from (value : Foo) =
| Bar value.get_thing here
|""".stripMargin.preprocessModule.analyse.bindings.head
.asInstanceOf[Method.Conversion]
val graph = conversionMethod
.unsafeGetMetadata(AliasAnalysis, "Missing aliasing info")
.unsafeAs[Info.Scope.Root]
.graph
val graphLinks = graph.links
val lambda = conversionMethod.body.asInstanceOf[IR.Function.Lambda]
val lambdaBody = lambda.body.asInstanceOf[IR.Expression.Block]
val app = lambdaBody.returnValue.asInstanceOf[IR.Application.Prefix]
"assign Info.Scope.Root metatata to the method" in {
val meta = conversionMethod.getMetadata(AliasAnalysis)
meta shouldBe defined
meta.get shouldBe an[Info.Scope.Root]
}
"assign Info.Scope.Child to all child scopes" in {
lambda.getMetadata(AliasAnalysis).get shouldBe an[Info.Scope.Child]
lambdaBody.getMetadata(AliasAnalysis).get shouldBe an[Info.Scope.Child]
}
"not allocate additional scopes unnecessarily" in {
graph.nesting shouldEqual 3
graph.numScopes shouldEqual 4
val topScope = graph.rootScope
val lambdaScope = lambda
.getMetadata(AliasAnalysis)
.get
.unsafeAs[Info.Scope.Child]
.scope
val blockScope = lambdaBody
.getMetadata(AliasAnalysis)
.get
.unsafeAs[Info.Scope.Child]
.scope
topScope shouldEqual lambdaScope
lambdaScope shouldEqual blockScope
}
"allocate new scopes where necessary" in {
val topScope = graph.rootScope
val arg1Scope = app.arguments.head
.getMetadata(AliasAnalysis)
.get
.unsafeAs[Info.Scope.Child]
.scope
@unused val arg2Scope = app
.arguments(1)
.getMetadata(AliasAnalysis)
.get
.unsafeAs[Info.Scope.Child]
.scope
topScope.childScopes should contain(arg1Scope)
topScope.childScopes should contain(arg2Scope)
}
"assign Info.Occurrence to definitions and usages of symbols" in {
lambda.arguments.foreach(arg =>
arg.getMetadata(AliasAnalysis).get.as[Info.Occurrence] shouldBe defined
)
val firstAppArg =
app.arguments.head.value.asInstanceOf[IR.Application.Prefix]
val innerAppArg = firstAppArg.arguments.head.value
innerAppArg
.getMetadata(AliasAnalysis)
.get
.as[Info.Occurrence] shouldBe defined
}
"create the correct usage links for resolvable entities" in {
val valueDefId = lambda
.arguments(1)
.getMetadata(AliasAnalysis)
.get
.unsafeAs[Info.Occurrence]
.id
val valueUseId = app.arguments.head.value
.asInstanceOf[IR.Application.Prefix]
.arguments
.head
.value
.getMetadata(AliasAnalysis)
.get
.unsafeAs[Info.Occurrence]
.id
graphLinks should contain(Link(valueUseId, 2, valueDefId))
}
"not resolve links for unknown symbols" in {
val unknownHereId = app
.arguments(1)
.value
.getMetadata(AliasAnalysis)
.get
.unsafeAs[Info.Occurrence]
.id
graph.linksFor(unknownHereId) shouldBe empty
graph.getOccurrence(unknownHereId).get shouldBe an[Occurrence.Use]
}
}
"Alias analysis on case expressions" should {
implicit val ctx: ModuleContext = mkModuleContext

View File

@ -3,7 +3,6 @@ package org.enso.compiler.test.pass.analyse
import org.enso.compiler.Passes
import org.enso.compiler.context.{FreshNameSupply, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.data.BindingsMap
import org.enso.compiler.data.BindingsMap.{Cons, ModuleMethod, PolyglotSymbol}
import org.enso.compiler.pass.analyse.BindingAnalysis
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
@ -60,17 +59,30 @@ class BindingAnalysisTest extends CompilerTest {
|Baz.foo = 123
|Bar.baz = Baz 1 2 . foo
|
|from (_ : Bar) = Foo 0 0 0
|from (value : Baz) = Foo value.x value.x value.y
|
|Foo.from (_ : Bar) = undefined
|
|foo = 123
|""".stripMargin.preprocessModule.analyse
ir.getMetadata(BindingAnalysis) shouldEqual Some(
BindingsMap(
List(Cons("Foo", 3), Cons("Bar", 0), Cons("Baz", 2)),
List(PolyglotSymbol("MyClass"), PolyglotSymbol("Renamed_Class")),
List(ModuleMethod("enso_project"), ModuleMethod("foo")),
ctx.module
)
val metadata = ir.unsafeGetMetadata(BindingAnalysis, "Should exist.")
metadata.types shouldEqual List(
Cons("Foo", 3),
Cons("Bar", 0),
Cons("Baz", 2)
)
metadata.polyglotSymbols shouldEqual List(
PolyglotSymbol("MyClass"),
PolyglotSymbol("Renamed_Class")
)
metadata.moduleMethods shouldEqual List(
ModuleMethod("enso_project"),
ModuleMethod("foo")
)
metadata.currentModule shouldEqual ctx.module
}
"properly assign module-level methods when a type with the same name as module is defined" in {

View File

@ -3,14 +3,15 @@ package org.enso.compiler.test.pass.analyse
import org.enso.compiler.Passes
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Module.Scope.Definition.Method
import org.enso.compiler.core.IR.Pattern
import org.enso.compiler.data.CompilerConfig
import org.enso.compiler.pass.PassConfiguration._
import org.enso.compiler.pass.analyse.DataflowAnalysis.DependencyInfo.Type.asStatic
import org.enso.compiler.pass.analyse.DataflowAnalysis.{
DependencyInfo,
DependencyMapping
}
import org.enso.compiler.pass.analyse.DataflowAnalysis.DependencyInfo.Type.asStatic
import org.enso.compiler.pass.analyse.{AliasAnalysis, DataflowAnalysis}
import org.enso.compiler.pass.optimise.ApplicationSaturation
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
@ -1587,4 +1588,110 @@ class DataflowAnalysisTest extends CompilerTest {
)
}
}
"Dataflow analysis of conversions" should {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
"""
|Foo.from (value : Bar) =
| Foo value 1
|""".stripMargin.preprocessModule.analyse
val depInfo = ir.getMetadata(DataflowAnalysis).get
// The method and its body
val conversion = ir.bindings.head.asInstanceOf[Method.Conversion]
val sourceType = conversion.sourceTypeName.asInstanceOf[IR.Name]
val lambda = conversion.body.asInstanceOf[IR.Function.Lambda]
val fnArgThis =
lambda.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified]
val fnArgValue =
lambda.arguments(1).asInstanceOf[IR.DefinitionArgument.Specified]
val fnBody = lambda.body.asInstanceOf[IR.Expression.Block]
// The `Foo` application
val fooExpr = fnBody.returnValue.asInstanceOf[IR.Application.Prefix]
val fooFunction = fooExpr.function.asInstanceOf[IR.Name]
val fooArg1 = fooExpr.arguments.head.asInstanceOf[IR.CallArgument.Specified]
val fooArg1Expr = fooArg1.value.asInstanceOf[IR.Name]
val fooArg2 = fooExpr.arguments(1).asInstanceOf[IR.CallArgument.Specified]
val fooArg2Expr = fooArg2.value.asInstanceOf[IR.Literal.Number]
// The global symbols
val fooSymbol = mkDynamicDep("Foo")
// The identifiers
val conversionId = mkStaticDep(conversion.getId)
val sourceTypeId = mkStaticDep(sourceType.getId)
val lambdaId = mkStaticDep(lambda.getId)
val fnArgThisId = mkStaticDep(fnArgThis.getId)
val fnArgValueId = mkStaticDep(fnArgValue.getId)
val fnBodyId = mkStaticDep(fnBody.getId)
val fooExprId = mkStaticDep(fooExpr.getId)
val fooFunctionId = mkStaticDep(fooFunction.getId)
val fooArg1Id = mkStaticDep(fooArg1.getId)
val fooArg1ExprId = mkStaticDep(fooArg1Expr.getId)
val fooArg2Id = mkStaticDep(fooArg2.getId)
val fooArg2ExprId = mkStaticDep(fooArg2Expr.getId)
// The info
val dependents = depInfo.dependents
val dependencies = depInfo.dependencies
"correctly identify global symbol direct dependents" in {
dependents.getDirect(fooSymbol) shouldEqual Some(Set(fooFunctionId))
}
"correctly identify global symbol direct dependencies" in {
dependencies.getDirect(fooSymbol) shouldBe empty
}
"correctly identify local direct dependents" in {
dependents.getDirect(conversionId) shouldBe empty
dependents.getDirect(sourceTypeId) shouldEqual Some(Set(conversionId))
dependents.getDirect(lambdaId) shouldEqual Some(Set(conversionId))
dependents.getDirect(fnArgThisId) shouldBe empty
dependents.getDirect(fnArgValueId) shouldBe Some(Set(fooArg1ExprId))
dependents.getDirect(fnBodyId) shouldBe Some(Set(lambdaId))
dependents.getDirect(fooExprId) shouldBe Some(Set(fnBodyId))
dependents.getDirect(fooFunctionId) shouldBe Some(Set(fooExprId))
dependents.getDirect(fooArg1Id) shouldBe Some(Set(fooExprId))
dependents.getDirect(fooArg1ExprId) shouldBe Some(Set(fooArg1Id))
dependents.getDirect(fooArg2Id) shouldBe Some(Set(fooExprId))
dependents.getDirect(fooArg2ExprId) shouldBe Some(Set(fooArg2Id))
}
"correctly identify local direct dependencies" in {
dependencies.getDirect(conversionId) shouldEqual Some(
Set(sourceTypeId, lambdaId)
)
dependencies.getDirect(sourceTypeId) shouldBe empty
dependencies.getDirect(lambdaId) shouldEqual Some(Set(fnBodyId))
dependencies.getDirect(fnBodyId) shouldEqual Some(Set(fooExprId))
dependencies.getDirect(fooExprId) shouldEqual Some(
Set(fooFunctionId, fooArg1Id, fooArg2Id)
)
dependencies.getDirect(fooFunctionId) shouldEqual Some(Set(fooSymbol))
dependencies.getDirect(fooArg1Id) shouldEqual Some(Set(fooArg1ExprId))
dependencies.getDirect(fooArg2Id) shouldEqual Some(Set(fooArg2ExprId))
dependencies.getDirect(fooArg1ExprId) shouldEqual Some(Set(fnArgValueId))
dependencies.getDirect(fooArg2ExprId) shouldBe empty
}
"associate the dependency info with every node in the IR" in {
conversion.hasDependencyInfo
sourceType.hasDependencyInfo
lambda.hasDependencyInfo
fnArgThis.hasDependencyInfo
fnArgValue.hasDependencyInfo
fnBody.hasDependencyInfo
fooExpr.hasDependencyInfo
fooFunction.hasDependencyInfo
fooArg1.hasDependencyInfo
fooArg1Expr.hasDependencyInfo
fooArg2.hasDependencyInfo
fooArg2Expr.hasDependencyInfo
}
}
}

View File

@ -95,16 +95,20 @@ class TailCallTest extends CompilerTest {
| _ -> d
|
|type MyAtom a b c
|
|Foo.from (value : Bar) = undefined
|""".stripMargin.preprocessModule.analyse
"mark methods as tail" in {
ir.bindings.head
.getMetadata(TailCall) shouldEqual Some(TailPosition.Tail)
ir.bindings.head.getMetadata(TailCall) shouldEqual Some(TailPosition.Tail)
}
"mark atoms as tail" in {
ir.bindings(1)
.getMetadata(TailCall) shouldEqual Some(TailPosition.Tail)
ir.bindings(1).getMetadata(TailCall) shouldEqual Some(TailPosition.Tail)
}
"mark conversions as tail" in {
ir.bindings(2).getMetadata(TailCall) shouldEqual Some(TailPosition.Tail)
}
}

View File

@ -4,6 +4,7 @@ import org.enso.compiler.Passes
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.pass.desugar.FunctionBinding
import org.enso.compiler.pass.resolve.{DocumentationComments, ModuleAnnotations}
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
@ -172,6 +173,30 @@ class FunctionBindingTest extends CompilerTest {
subArguments.head.suspended shouldBe true
}
"retain documentation comments and annotations associated with them" in {
val ir =
s"""
|## My documentation for this conversion.
|@My_Annotation
|My_Type.$from (that : Value) = that
|""".stripMargin.preprocessModule.desugar
ir.bindings.head shouldBe an[IR.Module.Scope.Definition.Method.Conversion]
val conversion = ir.bindings.head
.asInstanceOf[IR.Module.Scope.Definition.Method.Conversion]
val annotations =
conversion.unsafeGetMetadata(ModuleAnnotations, "Should be present.")
annotations.annotations.length shouldEqual 1
annotations.annotations.head.name shouldEqual "@My_Annotation"
val doc = conversion.unsafeGetMetadata(
DocumentationComments,
"Should be present."
)
doc.documentation shouldEqual " My documentation for this conversion."
}
"return an error if the conversion has no arguments" in {
val ir =
s"""My_Type.$from = a + b

View File

@ -39,7 +39,7 @@ class MethodDefinitionsTest extends CompilerTest {
* @param context the module context in which analysis takes place
* @return [[ir]], with tail call analysis metadata attached
*/
def analyse(implicit context: ModuleContext) = {
def analyse(implicit context: ModuleContext): IR.Module = {
MethodDefinitions.runModule(ir, context)
}
}
@ -52,6 +52,7 @@ class MethodDefinitionsTest extends CompilerTest {
val ir =
"""
|type Foo a b c
|type Bar
|
|Foo.my_method a b c = a + b + c
|
@ -60,10 +61,16 @@ class MethodDefinitionsTest extends CompilerTest {
|Test_Module.other_method = 11
|
|Does_Not_Exist.method = 32
|
|Foo.from (value : Bar) = undefined
|
|Bar.from (value : Does_Not_Exist) = undefined
|
|Does_Not_Exist.from (value : Foo) = undefined
|""".stripMargin.preprocessModule.analyse
"attach resolved atoms to the method definitions" in {
ir.bindings(1)
ir.bindings(2)
.asInstanceOf[IR.Module.Scope.Definition.Method.Explicit]
.methodReference
.typePointer
@ -75,15 +82,6 @@ class MethodDefinitionsTest extends CompilerTest {
)
)
)
ir.bindings(2)
.asInstanceOf[IR.Module.Scope.Definition.Method.Explicit]
.methodReference
.typePointer
.getMetadata(MethodDefinitions) shouldEqual Some(
BindingsMap.Resolution(
BindingsMap.ResolvedModule(ctx.module)
)
)
ir.bindings(3)
.asInstanceOf[IR.Module.Scope.Definition.Method.Explicit]
.methodReference
@ -94,9 +92,56 @@ class MethodDefinitionsTest extends CompilerTest {
)
)
ir.bindings(4)
.asInstanceOf[IR.Module.Scope.Definition.Method.Explicit]
.methodReference
.typePointer
.getMetadata(MethodDefinitions) shouldEqual Some(
BindingsMap.Resolution(
BindingsMap.ResolvedModule(ctx.module)
)
)
ir.bindings(5)
.asInstanceOf[IR.Module.Scope.Definition.Method.Explicit]
.methodReference
.typePointer shouldBe a[IR.Error.Resolution]
val conv1 = ir
.bindings(6)
.asInstanceOf[IR.Module.Scope.Definition.Method.Conversion]
conv1.methodReference.typePointer.getMetadata(
MethodDefinitions
) shouldEqual Some(
BindingsMap.Resolution(
BindingsMap.ResolvedConstructor(ctx.module, Cons("Foo", 3))
)
)
conv1.sourceTypeName.getMetadata(MethodDefinitions) shouldEqual Some(
BindingsMap.Resolution(
BindingsMap.ResolvedConstructor(ctx.module, Cons("Bar", 0))
)
)
val conv2 = ir
.bindings(7)
.asInstanceOf[IR.Module.Scope.Definition.Method.Conversion]
conv2.methodReference.typePointer.getMetadata(
MethodDefinitions
) shouldEqual Some(
BindingsMap.Resolution(
BindingsMap.ResolvedConstructor(ctx.module, Cons("Bar", 0))
)
)
conv2.sourceTypeName shouldBe an[IR.Error.Resolution]
val conv3 = ir
.bindings(8)
.asInstanceOf[IR.Module.Scope.Definition.Method.Conversion]
conv3.methodReference.typePointer shouldBe an[IR.Error.Resolution]
conv3.sourceTypeName.getMetadata(MethodDefinitions) shouldEqual Some(
BindingsMap.Resolution(
BindingsMap.ResolvedConstructor(ctx.module, Cons("Foo", 3))
)
)
}
}
}

View File

@ -68,6 +68,18 @@ class ModuleThisToHereTest extends CompilerTest {
| y = case this of
| A -> this * here
| z = y -> this + y
|
|from (other : Foo) =
| x = this * this + this
| y = case this of
| A -> this * here
| z = y -> this + y
|
|Foo.from (other : Foo) =
| 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 {
@ -78,13 +90,13 @@ class ModuleThisToHereTest extends CompilerTest {
.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
val thisOccurrences = children.collect { case n: IR.Name.This => n }
val hereOccurrences = children.collect { case n: IR.Name.Here => n }
thisOccurrences.length shouldEqual 0
hereOccurrences.length shouldEqual 7
}
"leave occurences of this and here untouched in non-module methods" in {
"leave occurrences of this and here untouched in non-module methods" in {
val method1 = ir
.bindings(1)
.asInstanceOf[IR.Module.Scope.Definition.Method.Explicit]
@ -92,11 +104,38 @@ class ModuleThisToHereTest extends CompilerTest {
.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
val thisOccurrences = children.collect { case n: IR.Name.This => n }
val hereOccurrences = children.collect { case n: IR.Name.Here => n }
thisOccurrences.length shouldEqual 6
hereOccurrences.length shouldEqual 1
}
"desugar this to here in module conversions" in {
val conv1 = ir
.bindings(3)
.asInstanceOf[IR.Module.Scope.Definition.Method.Conversion]
.body
.asInstanceOf[IR.Function.Lambda]
.body
val children = conv1.preorder
val thisOccurrences = children.collect { case n: IR.Name.This => n}
val hereOccurrences = children.collect { case n: IR.Name.Here => n}
thisOccurrences.length shouldEqual 0
hereOccurrences.length shouldEqual 7
}
"leave occurrences of this and here untouched in non-module conversions" in {
val conv2 = ir
.bindings(4)
.asInstanceOf[IR.Module.Scope.Definition.Method.Conversion]
.body
.asInstanceOf[IR.Function.Lambda]
.body
val children = conv2.preorder
val thisOccurrences = children.collect { case n: IR.Name.This => n}
val hereOccurrences = children.collect { case n: IR.Name.Here => n}
thisOccurrences.length shouldEqual 6
hereOccurrences.length shouldEqual 1
}
}
}

View File

@ -3,6 +3,7 @@ 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.core.IR.Module.Scope.Definition.Method
import org.enso.compiler.pass.resolve.OverloadsResolution
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
@ -79,6 +80,34 @@ class OverloadsResolutionTest extends CompilerTest {
}
}
"Conversion overload resolution" should {
implicit val ctx: ModuleContext = mkModuleContext
"allow overloads on the source type" in {
val ir =
"""Unit.from (value : Integer) = undefined
|Unit.from (value : Boolean) = undefined
|""".stripMargin.preprocessModule.resolve
ir.bindings.length shouldEqual 2
ir.bindings.head shouldBe a[Method.Conversion]
ir.bindings(1) shouldBe a[Method.Conversion]
}
"raise an error if there are multiple definitions with the same source type" in {
val ir =
"""Unit.from (value : Integer) = undefined
|Unit.from (value : Boolean) = undefined
|Unit.from (value : Boolean) = undefined
|""".stripMargin.preprocessModule.resolve
ir.bindings.length shouldEqual 3
ir.bindings.head shouldBe a[Method.Conversion]
ir.bindings(1) shouldBe a[Method.Conversion]
ir.bindings(2) shouldBe an[IR.Error.Redefined.Conversion]
}
}
"Atom overload resolution" should {
implicit val ctx: ModuleContext = mkModuleContext

View File

@ -140,7 +140,8 @@ class SuspendedArgumentsTest extends CompilerTest {
"""
|File.with_output_stream : Vector.Vector -> (Output_Stream -> Any ! File_Error) -> Any ! File_Error
|File.with_output_stream open_options action = undefined
|""".stripMargin.preprocessModule.resolve.bindings.head.asInstanceOf[Method.Explicit]
|""".stripMargin.preprocessModule.resolve.bindings.head
.asInstanceOf[Method.Explicit]
val bodyLam = ir.body.asInstanceOf[IR.Function.Lambda]
@ -149,6 +150,35 @@ class SuspendedArgumentsTest extends CompilerTest {
assert(!bodyLam.arguments(1).suspended, "open_options was suspended")
assert(!bodyLam.arguments(2).suspended, "action was suspended")
}
"work for conversion methods" in {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
"""File.from : Text -> Suspended -> Any
|File.from (value : Text) config=Nothing = undefined
|""".stripMargin.preprocessModule.resolve.bindings.head
.asInstanceOf[Method.Conversion]
val bodyLam = ir.body.asInstanceOf[IR.Function.Lambda]
val args = bodyLam.arguments
args.length shouldEqual 3
assert(!args(1).suspended, "the source argument was suspended")
assert(args(2).suspended, "the config argument was not suspended")
}
"raise an error if a conversion method marks its source argument as suspended" in {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
"""File.from (~value : Text) = undefined
|""".stripMargin.preprocessModule.resolve.bindings.head
ir shouldBe an[IR.Error.Conversion]
ir.asInstanceOf[IR.Error.Conversion]
.reason shouldBe an[IR.Error.Conversion.SuspendedSourceArgument]
}
}
"Suspended arguments resolution in expressions" should {