diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala index 8703e8cb5d9..b45232b0d21 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala @@ -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 diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala index 277a549603a..a61370766e0 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala @@ -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() diff --git a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala index 709e7c97bd4..3f3c014d459 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala @@ -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. * diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala index 1ad16c4be75..262c9d1392d 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala @@ -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) { diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/CachePreferenceAnalysis.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/CachePreferenceAnalysis.scala index 66ba08dadc0..87472c31e2c 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/CachePreferenceAnalysis.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/CachePreferenceAnalysis.scala @@ -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 diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala index a4bf72c03dd..d08a1ff5714 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala @@ -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) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/TailCall.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/TailCall.scala index 5eba74f0a0c..eeb90440f5c 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/TailCall.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/TailCall.scala @@ -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 diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/DocumentationComments.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/DocumentationComments.scala index d6c68cf2232..e4665de8493 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/DocumentationComments.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/DocumentationComments.scala @@ -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 => diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala index 34541cede24..fb57301658c 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala @@ -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. * diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/ModuleThisToHere.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/ModuleThisToHere.scala index bae4f40228f..c3594639efb 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/ModuleThisToHere.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/ModuleThisToHere.scala @@ -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 } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/OverloadsResolution.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/OverloadsResolution.scala index d166b2d423c..bfd1bb84cf1 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/OverloadsResolution.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/OverloadsResolution.scala @@ -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 ) } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala index 3429c0ed8ba..633f6f59948 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala @@ -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, _, _, _, _) => diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/AliasAnalysisTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/AliasAnalysisTest.scala index 5652febe40d..caaee0fe8be 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/AliasAnalysisTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/AliasAnalysisTest.scala @@ -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 diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala index 3877d6582d4..559aac84a3e 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala @@ -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 { diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/DataflowAnalysisTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/DataflowAnalysisTest.scala index c9dacf5b029..6ea695a2258 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/DataflowAnalysisTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/DataflowAnalysisTest.scala @@ -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 + } + } } diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/TailCallTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/TailCallTest.scala index 9a1b9f30980..137c15c5bcb 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/TailCallTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/TailCallTest.scala @@ -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) } } diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/FunctionBindingTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/FunctionBindingTest.scala index 3d3b0ab243b..8ab592f15fe 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/FunctionBindingTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/FunctionBindingTest.scala @@ -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 diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/MethodDefinitionsTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/MethodDefinitionsTest.scala index 79636165956..c39d82cf69f 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/MethodDefinitionsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/MethodDefinitionsTest.scala @@ -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)) + ) + ) } } } diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/ModuleThisToHereTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/ModuleThisToHereTest.scala index 8d4b3870682..77167609110 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/ModuleThisToHereTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/ModuleThisToHereTest.scala @@ -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 + } } } diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/OverloadsResolutionTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/OverloadsResolutionTest.scala index f00cb1a1afe..391564c01af 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/OverloadsResolutionTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/OverloadsResolutionTest.scala @@ -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 diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/SuspendedArgumentsTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/SuspendedArgumentsTest.scala index 11f27645de3..b1669a74e7f 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/SuspendedArgumentsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/SuspendedArgumentsTest.scala @@ -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 {