From 9dee1911f8ccdd0d03ca26eaf01c45ac806daefd Mon Sep 17 00:00:00 2001 From: Ara Adkins Date: Thu, 13 Feb 2020 09:52:05 +0000 Subject: [PATCH] Add smart constructors for the core nodes (#480) --- build.sbt | 10 + .../src/main/scala/org/enso/graph/Graph.scala | 91 +- .../org/enso/graph/definition/Macro.scala | 28 +- .../test/scala/org/enso/graph/GraphTest.scala | 2 +- .../org/enso/graph/GraphTestDefinition.scala | 64 +- .../org/enso/syntax/text/DocParserTests.scala | 1 - .../main/scala/org/enso/core/CoreGraph.scala | 77 +- .../scala/org/enso/compiler/bench/.gitkeep | 0 .../scala/org/enso/compiler/core/Core.scala | 1813 ++++++++++++++++- .../compiler/test/core/CorePrimTest.scala | 81 +- .../enso/compiler/test/core/CoreTest.scala | 4 +- .../test/core/SmartConstructorsTest.scala | 1472 +++++++++++++ 12 files changed, 3550 insertions(+), 93 deletions(-) create mode 100644 engine/runtime/src/bench/scala/org/enso/compiler/bench/.gitkeep create mode 100644 engine/runtime/src/test/scala/org/enso/compiler/test/core/SmartConstructorsTest.scala diff --git a/build.sbt b/build.sbt index 456c56bf709..b19b553ea05 100644 --- a/build.sbt +++ b/build.sbt @@ -312,6 +312,9 @@ lazy val graph = (project in file("common/graph/")) "com.github.ghik" % "silencer-lib" % silencerVersion % Provided cross CrossVersion.full ), addCompilerPlugin( + "org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full + ), + addCompilerPlugin ( "org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full ), addCompilerPlugin("io.tryp" % "splain" % "0.5.0" cross CrossVersion.patch), @@ -394,6 +397,9 @@ lazy val core_definition = (project in file("engine/core-definition")) addCompilerPlugin( "org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full ), + addCompilerPlugin( + "org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full + ), addCompilerPlugin("io.tryp" % "splain" % "0.5.0" cross CrossVersion.patch), scalacOptions ++= Seq( "-P:splain:infix:true", @@ -403,6 +409,7 @@ lazy val core_definition = (project in file("engine/core-definition")) ) ) .dependsOn(graph) + .dependsOn(syntax.jvm) lazy val runtime = (project in file("engine/runtime")) .configs(Benchmark) @@ -444,6 +451,9 @@ lazy val runtime = (project in file("engine/runtime")) addCompilerPlugin( "org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full ), + addCompilerPlugin( + "org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full + ), addCompilerPlugin("io.tryp" % "splain" % "0.5.0" cross CrossVersion.patch), scalacOptions ++= Seq( "-P:splain:infix:true", diff --git a/common/graph/src/main/scala/org/enso/graph/Graph.scala b/common/graph/src/main/scala/org/enso/graph/Graph.scala index 42476cd4a46..6b40ee073e8 100644 --- a/common/graph/src/main/scala/org/enso/graph/Graph.scala +++ b/common/graph/src/main/scala/org/enso/graph/Graph.scala @@ -19,6 +19,8 @@ import shapeless.nat._ * as much as possible. * - Basic equality testing (that should be overridden as needed). * - An ability to define fields that store complex data such as `String`. + * - Add a `Default` typeclass, and ensure that all component fields are + * instances of it. Fields should then be initialised using it. */ /** This file contains the implementation of an incredibly generic graph. @@ -357,8 +359,6 @@ object Graph { @newtype final case class Ref[G <: Graph, C <: Component](ix: Int) - // === Refined === - /** Type refinement for component references. * * Type refinement is used to add additional information to a [[Component]] @@ -370,11 +370,14 @@ object Graph { * encoded having the following type `Refined[Shape, App, Node]`. */ @newtype - final case class Refined[C <: Component.Field, Spec, T](wrapped: T) + final case class Refined[F <: Component.Field, Spec, T](wrapped: T) object Refined { - implicit def unwrap[C <: Component.Field, S, T]( - t: Refined[C, S, T] + implicit def unwrap[F <: Component.Field, S <: F, T]( + t: Refined[F, S, T] ): T = { t.wrapped } + + def wrap[F <: Component.Field, S <: F, T](t: T): Refined[F, S, T] = + Refined(t) } // === List === @@ -721,7 +724,8 @@ object Graph { ev3: HListTakeUntil.Aux[C, ComponentList, PrevComponentList], ev4: hlist.Length.Aux[PrevComponentList, ComponentIndex], componentIndexEv: nat.ToInt[ComponentIndex], - componentSizeEv: KnownSize[FieldList] + componentSizeEv: KnownSize[FieldList], + listContainsComponent: Selector[ComponentList, C] ): HasComponent[G, C] = new HasComponent[G, C] { val componentIndex = componentIndexEv() val componentSize = componentSizeEv.asInt @@ -751,7 +755,8 @@ object Graph { implicit ev1: Component.Field.List.Aux[G, C, FieldList], evx: HasComponent[G, C], - fieldOffsetEv: SizeUntil[F, FieldList] + fieldOffsetEv: SizeUntil[F, FieldList], + containsField: Selector[FieldList, F] ): HasComponentField[G, C, F] = new HasComponentField[G, C, F] { val componentIndex = evx.componentIndex val componentSize = evx.componentSize @@ -783,4 +788,76 @@ object Graph { val sizes = info.componentSize +: tail.sizes } } + + /** Allows casting between variant cases without actually mutating the + * underlying structure. + * + * This is a very unsafe operation and should be used with care. + * + * @param component the component to cast the variant field in + * @param ev evidence that component [[C]] has field [[G]] in graph [[G]] + * @tparam G the graph type + * @tparam C the component type + * @tparam F the field type + */ + implicit class VariantCast[ + G <: Graph, + C <: Component, + F <: Component.Field + ](val component: Component.Ref[G, C])( + implicit ev: HasComponentField[G, C, F], + graph: GraphData[G] + ) { + + /** Checks if [[component]] is in the variant case denoted by the type + * [[V]]. + * + * @param variantIndexed information that [[F]] is indeed a variant, with + * [[V]] as a valid case + * @tparam V the type of the variant case in question + * @return `true` if [[component]] is of the form denoted by [[V]], `false` + * otherwise + */ + def is[V <: F]( + implicit variantIndexed: VariantIndexed[F, V] + ): Boolean = { + graph.unsafeReadField[C, F](component) == variantIndexed.ix + } + + /** Casts the variant field [[F]] to behave as the variant branch [[V]]. + * + * It should be noted that this is purely a superficial cast, and does not + * affect the underlying graph. This means that [[C]] will still pattern + * match as if it was its original variant branch. + * + * @param variantIndexed information that [[F]] is indeed a variant, with + * [[V]] as a valid case + * @tparam V the type of the variant case in question + * @return the component [[component]] refined to be the variant branch + * [[V]] + */ + def unsafeAs[V <: F]( + implicit variantIndexed: VariantIndexed[F, V] + ): Component.Refined[F, V, Component.Ref[G, C]] = { + Component.Refined[F, V, Component.Ref[G, C]](component) + } + + /** Performs a checked cast of [[component]] to the variant state denoted + * by [[V]]. + * + * @param variantIndexed information that [[F]] is indeed a variant, with + * [[V]] as a valid case + * @tparam V the type of the variant case in question + * @return [[Some]] if [[component]] is a [[V]], otherwise [[None]] + */ + def as[V <: F]( + implicit variantIndexed: VariantIndexed[F, V] + ): Option[Component.Refined[F, V, Component.Ref[G, C]]] = { + if (is[V]) { + Some(unsafeAs[V]) + } else { + None + } + } + } } diff --git a/common/graph/src/main/scala/org/enso/graph/definition/Macro.scala b/common/graph/src/main/scala/org/enso/graph/definition/Macro.scala index 4f86d008d4f..1b9d031f67f 100644 --- a/common/graph/src/main/scala/org/enso/graph/definition/Macro.scala +++ b/common/graph/src/main/scala/org/enso/graph/definition/Macro.scala @@ -230,7 +230,11 @@ object Macro { } } - /** Generates a getter for an element of a non-variant field. + /** Generates a getter for an element of a field. + * + * In the case where the field is _not_ simple, the subfield offsets are + * all incremented by one, to account for the fact that the first index + * in a non-simple (variant) field encodes the variant branch. * * @param paramDef the definition of the subfield * @param enclosingTypeName the name of the field type @@ -277,7 +281,7 @@ object Macro { $graphTermName.Component.Ref( graph.unsafeReadFieldByIndex[C, $enclosingTypeName]( $graphTermName.Component.Refined.unwrap(node), - $index + ${index + 1} ) ) } @@ -334,7 +338,7 @@ object Macro { $graphTermName.Component.Ref( graph.unsafeReadFieldByIndex[C, $enclosingTypeName]( $graphTermName.Component.Refined.unwrap(node).ix, - $index + ${index + 1} ) ) } @@ -343,7 +347,11 @@ object Macro { } } - /** Generates a setter for an element of a non-variant field. + /** Generates a setter for an element of field. + * + * In the case where the field is _not_ simple, the subfield offsets are + * all incremented by one, to account for the fact that the first index + * in a non-simple (variant) field encodes the variant branch. * * @param paramDef the definition of the subfield * @param enclosingTypeName the name of the field type @@ -388,7 +396,7 @@ object Macro { ): Unit = { graph.unsafeWriteFieldByIndex[C, $enclosingTypeName]( $graphTermName.Component.Refined.unwrap(node).ix, - $index, + ${index + 1}, value ) } @@ -441,7 +449,7 @@ object Macro { ): Unit = { graph.unsafeWriteFieldByIndex[C, $enclosingTypeName]( $graphTermName.Component.Refined.unwrap(node).ix, - $index, + ${index + 1}, value.ix ) } @@ -459,12 +467,6 @@ object Macro { */ def makeTypeAccessorName(fieldName: TypeName): String = { StringUtils.uncapitalize(fieldName.toString) -// val matcher = "([A-Z])(.*)".r -// -// matcher.findFirstMatchIn(fieldName.toString) match { -// case Some(t) => s"${t.group(1).toLowerCase()}${t.group(2)}" -// case None => fieldName.toString.toLowerCase -// } } /** Generates accessor methods for the 'value class', the one that can @@ -527,7 +529,7 @@ object Macro { } /** Determines whether the parameter definition is defining an opaque - * type. An opaque type is one not stored in the graph represntation. + * type. An opaque type is one not stored in the graph representation. * * @param paramDef the definition to check * @return `true` if `paramDef` defines an opauqe type, otherwise `false` diff --git a/common/graph/src/test/scala/org/enso/graph/GraphTest.scala b/common/graph/src/test/scala/org/enso/graph/GraphTest.scala index 01357101532..d7e38b13914 100644 --- a/common/graph/src/test/scala/org/enso/graph/GraphTest.scala +++ b/common/graph/src/test/scala/org/enso/graph/GraphTest.scala @@ -95,7 +95,7 @@ class GraphTest extends FlatSpec with Matchers { case GraphImpl.Node.Shape.App.any(n1) => n1.fn } - refinedResult shouldEqual 1 + refinedResult shouldEqual 0 } "Component fields" can "be accessed properly" in { diff --git a/common/graph/src/test/scala/org/enso/graph/GraphTestDefinition.scala b/common/graph/src/test/scala/org/enso/graph/GraphTestDefinition.scala index 2472622eaec..dd602e77598 100644 --- a/common/graph/src/test/scala/org/enso/graph/GraphTestDefinition.scala +++ b/common/graph/src/test/scala/org/enso/graph/GraphTestDefinition.scala @@ -173,9 +173,9 @@ object GraphTestDefinition { // ev: PrimGraph.HasComponentField[G, C, Shape] // ): Edge[G] = // PrimGraph.Component.Ref( -// graph.primUnsafeReadField[C, Shape]( +// graph.primUnsafeReadFieldByIndex[C, Shape]( // PrimGraph.Component.Refined.unwrap(node).ix, -// 0 +// 1 // ) // ) // @@ -183,9 +183,9 @@ object GraphTestDefinition { // implicit graph: PrimGraph.GraphData[G], // ev: PrimGraph.HasComponentField[G, C, Shape] // ): Unit = -// graph.primUnsafeWriteField[C, Shape]( +// graph.primUnsafeWriteFieldByIndex[C, Shape]( // PrimGraph.Component.Refined.unwrap(node).ix, -// 0, +// 1, // value.ix // ) // @@ -194,9 +194,9 @@ object GraphTestDefinition { // ev: PrimGraph.HasComponentField[G, C, Shape] // ): Edge[G] = // PrimGraph.Component.Ref( -// graph.primUnsafeReadField[C, Shape]( +// graph.primUnsafeReadFieldByIndex[C, Shape]( // PrimGraph.Component.Refined.unwrap(node).ix, -// 1 +// 2 // ) // ) // @@ -204,9 +204,9 @@ object GraphTestDefinition { // implicit graph: PrimGraph.GraphData[G], // ev: PrimGraph.HasComponentField[G, C, Shape] // ): Unit = -// graph.primUnsafeWriteField[C, Shape]( +// graph.primUnsafeWriteFieldByIndex[C, Shape]( // PrimGraph.Component.Refined.unwrap(node).ix, -// 1, +// 2, // value.ix // ) // @@ -265,9 +265,9 @@ object GraphTestDefinition { // ev: PrimGraph.HasComponentField[G, C, Shape] // ): Edge[G] = // PrimGraph.Component.Ref( -// graph.primUnsafeReadField[C, Shape]( +// graph.primUnsafeReadFieldByIndex[C, Shape]( // PrimGraph.Component.Refined.unwrap(node).ix, -// 0 +// 1 // ) // ) // @@ -275,9 +275,9 @@ object GraphTestDefinition { // implicit graph: PrimGraph.GraphData[G], // ev: PrimGraph.HasComponentField[G, C, Shape] // ): Unit = -// graph.primUnsafeWriteField[C, Shape]( +// graph.primUnsafeWriteFieldByIndex[C, Shape]( // PrimGraph.Component.Refined.unwrap(node).ix, -// 0, +// 2, // value.ix // ) // @@ -286,11 +286,7 @@ object GraphTestDefinition { // ev: PrimGraph.HasComponentField[G, C, Shape] // ): CentreVal[G] = { // CentreVal( -// PrimGraph.Component.Ref( -// graph.primUnsafeReadField[C, Shape]( -// PrimGraph.Component.Refined.unwrap(node).ix, -// 0 -// ) +// this.fn // ) // ) // } @@ -299,11 +295,7 @@ object GraphTestDefinition { // implicit graph: PrimGraph.GraphData[G], // ev: PrimGraph.HasComponentField[G, C, Shape] // ): Unit = { -// graph.primUnsafeWriteField[C, Shape]( -// PrimGraph.Component.Refined.unwrap(node).ix, -// 0, -// value.fn.ix -// ) +// this.fn = value.fn // } // } // } @@ -362,9 +354,9 @@ object GraphTestDefinition { // ev: PrimGraph.HasComponentField[G, C, Shape] // ): Edge[G] = { // PrimGraph.Component.Ref( -// graph.primUnsafeReadField[C, Shape]( +// graph.primUnsafeReadFieldByIndex[C, Shape]( // PrimGraph.Component.Refined.unwrap(node).ix, -// 0 // as the other field is opaque +// 1 // as the other field is opaque // ) // ) // } @@ -373,9 +365,9 @@ object GraphTestDefinition { // implicit graph: PrimGraph.GraphData[G], // ev: PrimGraph.HasComponentField[G, C, Shape] // ): Unit = { -// graph.primUnsafeWriteField[C, Shape]( +// graph.primUnsafeWriteFieldByIndex[C, Shape]( // PrimGraph.Component.Refined.unwrap(node).ix, -// 0, +// 1, // value.ix // ) // } @@ -422,7 +414,7 @@ object GraphTestDefinition { // ev: PrimGraph.HasComponentField[G, C, ParentLink] // ): Edge[G] = { // PrimGraph.Component.Ref( - // graph.unsafeReadField[C, ParentLink](node.ix, 0) + // graph.unsafeReadFieldByIndex[C, ParentLink](node.ix, 0) // ) // } // @@ -430,7 +422,7 @@ object GraphTestDefinition { // implicit graph: PrimGraph.GraphData[G], // ev: PrimGraph.HasComponentField[G, C, ParentLink] // ): Unit = { - // graph.unsafeWriteField[C, ParentLink](node.ix, 0, value.ix) + // graph.unsafeWriteFieldByIndex[C, ParentLink](node.ix, 0, value.ix) // } // // def parentLink( @@ -480,18 +472,18 @@ object GraphTestDefinition { // def line_=(value: Int)( // implicit graph: PrimGraph.GraphData[G], // ev: PrimGraph.HasComponentField[G, C, Location] -// ): Unit = graph.unsafeWriteField[C, Location](node.ix, 0, value) +// ): Unit = graph.unsafeWriteFieldByIndex[C, Location](node.ix, 0, value) // // def column( // implicit graph: PrimGraph.GraphData[G], // ev: PrimGraph.HasComponentField[G, C, Location] // ): Int = -// graph.unsafeReadField[C, Location](node.ix, 1) +// graph.unsafeReadFieldByIndex[C, Location](node.ix, 1) // // def column_=(value: Int)( // implicit graph: PrimGraph.GraphData[G], // ev: PrimGraph.HasComponentField[G, C, Location] -// ): Unit = graph.unsafeWriteField[C, Location](node.ix, 1, value) +// ): Unit = graph.unsafeWriteFieldByIndex[C, Location](node.ix, 1, value) // // def location( // implicit graph: PrimGraph.GraphData[G], @@ -593,26 +585,28 @@ object GraphTestDefinition { // ev: PrimGraph.HasComponentField[G, C, Shape] // ): Node[G] = // PrimGraph.Component.Ref( -// graph.unsafeReadField[C, Shape](node.ix, 0) +// graph.unsafeReadFieldByIndex[C, Shape](node.ix, 0) // ) // // def source_=(value: Node[G])( // implicit graph: PrimGraph.GraphData[G], // ev: PrimGraph.HasComponentField[G, C, Shape] -// ): Unit = graph.unsafeWriteField[C, Shape](node.ix, 0, value.ix) +// ): Unit = +// graph.unsafeWriteFieldByIndex[C, Shape](node.ix, 0, value.ix) // // def target( // implicit graph: PrimGraph.GraphData[G], // ev: PrimGraph.HasComponentField[G, C, Shape] // ): Node[G] = // PrimGraph.Component.Ref( -// graph.unsafeReadField[C, Shape](node.ix, 1) +// graph.unsafeReadFieldByIndex[C, Shape](node.ix, 1) // ) // // def target_=(value: Node[G])( // implicit graph: PrimGraph.GraphData[G], // ev: PrimGraph.HasComponentField[G, C, Shape] -// ): Unit = graph.unsafeWriteField[C, Shape](node.ix, 1, value.ix) +// ): Unit = +// graph.unsafeWriteFieldByIndex[C, Shape](node.ix, 1, value.ix) // // def shape( // implicit graph: PrimGraph.GraphData[G], diff --git a/common/syntax/specialization/shared/src/test/scala/org/enso/syntax/text/DocParserTests.scala b/common/syntax/specialization/shared/src/test/scala/org/enso/syntax/text/DocParserTests.scala index c3543bc9d7b..b7ddd1f1c39 100644 --- a/common/syntax/specialization/shared/src/test/scala/org/enso/syntax/text/DocParserTests.scala +++ b/common/syntax/specialization/shared/src/test/scala/org/enso/syntax/text/DocParserTests.scala @@ -18,7 +18,6 @@ class DocParserTests extends FlatSpec with Matchers { val output = DocParser.run(input) output match { case Result(_, Result.Success(value)) => - pprint.pprintln(value) assert(value == result) assert(value.show() == new Reader(input).toString()) case _ => diff --git a/engine/core-definition/src/main/scala/org/enso/core/CoreGraph.scala b/engine/core-definition/src/main/scala/org/enso/core/CoreGraph.scala index bd525598a88..4ff2c47234d 100644 --- a/engine/core-definition/src/main/scala/org/enso/core/CoreGraph.scala +++ b/engine/core-definition/src/main/scala/org/enso/core/CoreGraph.scala @@ -3,8 +3,8 @@ package org.enso.core import org.enso.graph.definition.Macro.{component, field, genGraph, opaque} import org.enso.graph.{Sized, VariantIndexed, Graph => PrimGraph} import shapeless.{::, HNil} +import org.enso.syntax.text.AST -// TODO [AA] More detailed semantic descriptions for each node shape in future. object CoreGraph { @genGraph object Definition { @@ -38,6 +38,9 @@ object CoreGraph { */ @opaque case class Parent(opaque: Vector[Int]) + /** Storage for raw AST nodes. */ + @opaque case class Ast(opaque: AST) + // ======================================================================== // === Node =============================================================== // ======================================================================== @@ -176,7 +179,7 @@ object CoreGraph { /** An import statement. * * @param segments the segments of the import path, represented as a - * [[NameLiteral]]. + * [[MetaList]]. */ case class Import(segments: Link[G]) @@ -199,7 +202,8 @@ object CoreGraph { /** An expanded-form type definition, with a body. * * @param name the name of the aggregate type - * @param typeParams the type parameters to the definition + * @param typeParams the type parameters to the definition, as a + * [[MetaList]] of bindings * @param body the body of the type definition, represented as a * [[MetaList]] of bindings */ @@ -211,7 +215,7 @@ object CoreGraph { // === Typing ========================================================= - /** A type signature. + /** The ascription of a type to a value. * * @param typed the expression being ascribed a type * @param sig the signature being ascribed to [[typed]] @@ -446,7 +450,7 @@ object CoreGraph { /** A case branch. * * All case patterns will initially be desugared to a - * [[StructuralMatch]] and will be refined during further desugaring + * [[StructuralPattern]] and will be refined during further desugaring * passes, some of which may depend on type checking. * * @param pattern the pattern to match the scrutinee against @@ -459,7 +463,7 @@ object CoreGraph { * @param matchExpression the expression representing the possible * structure of the scrutinee */ - case class StructuralMatch(matchExpression: Link[G]) + case class StructuralPattern(matchExpression: Link[G]) /** A pattern that matches on the scrutinee purely based on a type * subsumption judgement. @@ -467,7 +471,7 @@ object CoreGraph { * @param matchExpression the expression representing the possible type * of the scrutinee */ - case class TypeMatch(matchExpression: Link[G]) + case class TypePattern(matchExpression: Link[G]) /** A pattern that matches on the scrutinee based on a type subsumption * judgement and assigns a new name to it for use in the branch. @@ -475,10 +479,10 @@ object CoreGraph { * @param matchExpression the expression representing the possible type * of the scrutinee, and its new name */ - case class NamedMatch(matchExpression: Link[G]) + case class NamedPattern(matchExpression: Link[G]) /** A pattern that matches on any scrutinee. */ - case class FallbackMatch() + case class FallbackPattern() // === Comments ======================================================= @@ -502,17 +506,61 @@ object CoreGraph { /** A syntax error. * - * @param errorNode the node representation of the syntax error + * @param errorAst the raw AST representation of the syntax error */ - case class SyntaxError(errorNode: Link[G]) + case class SyntaxError(errorAst: OpaqueData[AST, AstStorage]) - // TODO [AA] Fill in the error types as they become evident + /** Returned on an attempt to construct erroneous core. + * + * @param erroneousCore a [[MetaList]] containing the one-or-more core + * nodes that were in an incorrect format + */ + case class ConstructionError(erroneousCore: Link[G]) } // ====================================================================== // === Utility Functions ================================================ // ====================================================================== + /** Adds a link as a parent of the provided node. + * + * This should _only_ be used when the [[target]] field of [[link]] + * points to [[node]]. + * + * @param node the node to add a parent to + * @param link the link to add as a parent + * @param graph the graph in which this takes place + * @param map the graph's parent storage + */ + def addParent( + node: Node[CoreGraph], + link: Link[CoreGraph] + )( + implicit graph: PrimGraph.GraphData[CoreGraph], + map: ParentStorage + ): Unit = { + import Node.ParentLinks._ + + node.parents = node.parents :+ link.ix + } + + /** Adds a node to the graph with its shape already set to a given shape. + * + * @param graph the graph to add the node to + * @param ev evidence that the variant field is indexed + * @tparam V the shape to set the node to + * @return a refined node reference + */ + def addRefined[V <: Node.Shape]( + implicit graph: PrimGraph.GraphData[CoreGraph], + ev: VariantIndexed[Node.Shape, V] + ): PrimGraph.Component.Refined[Node.Shape, V, Node[CoreGraph]] = { + val node = graph.addNode() + + setShape[V](node) + PrimGraph.Component.Refined[Node.Shape, V, Node[CoreGraph]](node) + } + /** Sets the shape of the provided [[node]] to [[Shape]]. * * @param node the node to set @@ -538,8 +586,9 @@ object CoreGraph { node: Node[CoreGraph] )(implicit graph: PrimGraph.GraphData[CoreGraph]): Boolean = { node match { - case Shape.SyntaxError.any(_) => true - case _ => false + case Shape.SyntaxError.any(_) => true + case Shape.ConstructionError.any(_) => true + case _ => false } } diff --git a/engine/runtime/src/bench/scala/org/enso/compiler/bench/.gitkeep b/engine/runtime/src/bench/scala/org/enso/compiler/bench/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/engine/runtime/src/main/scala/org/enso/compiler/core/Core.scala b/engine/runtime/src/main/scala/org/enso/compiler/core/Core.scala index f2f015cb00a..04ae914744f 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/core/Core.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/core/Core.scala @@ -1,6 +1,23 @@ package org.enso.compiler.core +import cats.data.NonEmptyList +import org.enso.core.CoreGraph.DefinitionGen.Node.{ + Shape => NodeShape, + LocationVal +} +import org.enso.core.CoreGraph.{DefinitionGen => CoreDef} import org.enso.graph.{Graph => PrimGraph} +import org.enso.syntax.text.{AST, Location => AstLocation} + +import scala.annotation.tailrec + +// TODO [AA] Detailed semantic descriptions for each node shape in future. +// TODO [AA] Refactor over time to remove as much boilerplate as possible. +// TODO [AA] Eventually refactor graph macro generation so as to allow the +// trait-extension based approach to implicit imports. +// TODO [AA] Need to present a nice interface +// - Copy subsection of graph +// - Check equality for subsection of graph /** [[Core]] is the sophisticated internal representation supported by the * compiler. @@ -8,7 +25,1799 @@ import org.enso.graph.{Graph => PrimGraph} * It is a structure designed to be amenable to program analysis and * transformation and features: * - High performance on a mutable graph structure. - * - High levels of type-safety to reduce the incidence of bugs. * - Mutable links to represent program structure. + * + * To use core properly you will need to have the following imports in scope. + * These serve to bring the correct set of implicits into scope: + * + * {{{ + * import Core._ + * import CoreDef.Link.Shape._ + * import CoreDef.Node.Location._ + * import CoreDef.Node.ParentLinks._ + * import CoreDef.Node.Shape._ + * import org.enso.core.CoreGraph.{DefinitionGen => CoreDef} + * import org.enso.graph.{Graph => PrimGraph} + * import PrimGraph.Component.Refined._ + * }}} + * + * Please note that the smart constructor functions are _intentionally_ named + * using upper-case so as to signify that they construct a value. */ -class Core {} +class Core { + + // ========================================================================== + // === Graph Storage ======================================================== + // ========================================================================== + + implicit val graph: Core.GraphData = PrimGraph[Core.Graph]() + + implicit val literalStorage: Core.LiteralStorage = CoreDef.LiteralStorage() + implicit val nameStorage: Core.NameStorage = CoreDef.NameStorage() + implicit val parentStorage: Core.ParentStorage = CoreDef.ParentStorage() + implicit val astStorage: Core.AstStorage = CoreDef.AstStorage() +} +object Core { + + import CoreDef.Link.Shape._ + import CoreDef.Node.Location._ + import CoreDef.Node.ParentLinks._ + import CoreDef.Node.Shape._ + import PrimGraph.Component.Refined._ + + // ========================================================================== + // === Useful Type Aliases ================================================== + // ========================================================================== + + // === Graph ================================================================ + + type Graph = CoreDef.CoreGraph + type GraphData = PrimGraph.GraphData[Graph] + + // === Components =========================================================== + + type Node = CoreDef.Node[Graph] + type Link = CoreDef.Link[Graph] + type RefinedNode[V <: CoreDef.Node.Shape] = + PrimGraph.Component.Refined[NodeShape, V, Node] + + // === Errors =============================================================== + + type ErrorOrRefined[Err <: CoreDef.Node.Shape, T <: CoreDef.Node.Shape] = + Either[RefinedNode[Err], RefinedNode[T]] + type ConsErrOr[T <: CoreDef.Node.Shape] = + ErrorOrRefined[NodeShape.ConstructionError, T] + + // === Opaque Storage ======================================================= + + type LiteralStorage = CoreDef.LiteralStorage + type NameStorage = CoreDef.NameStorage + type ParentStorage = CoreDef.ParentStorage + type AstStorage = CoreDef.AstStorage + + // === Location ============================================================= + + type Location = LocationVal[Graph] + + // ========================================================================== + // === Node ================================================================= + // ========================================================================== + + /** Functionality for working with nodes. */ + object Node { + + /** Smart constructors to create nodes of various shapes. */ + //noinspection DuplicatedCode + object New { + + // === Meta Shapes ====================================================== + + /** Creates a node that has no particular shape. + * + * @return an empty node + */ + def Empty()(implicit core: Core): RefinedNode[NodeShape.Empty] = { + val node = CoreDef.Node.addRefined[NodeShape.Empty] + + node.location = Constants.invalidLocation + node.parents = Vector() + + node + } + + /** Creates a representation of a cons cell for building linked lists on + * the core graph. + * + * These should be used _very_ sparingly, if at all, but they provide a + * way to store dynamically-sized core components providing they can be + * broken down into statically sized components. + * + * The [[tail]] parameter should always point to either another node + * with shape [[MetaList]] or a node with shape [[MetaNil]]. + * + * It should be noted that, given that each [[Node]] contains a field + * of [[ParentLinks]], that constructing this properly provides a + * doubly-linked list, as no [[MetaList]] or [[MetaNil]] should have + * more than one parent. + * + * The location contained in this node is _invalid_ as it does not + * represent any location in the program source. + * + * @param head the current, arbitrary, element in the list + * @param tail the rest of the list + * @param core an implicit instance of core + * @return a node representing an on-graph meta list + */ + def MetaList( + head: Node, + tail: Node + )(implicit core: Core): ConsErrOr[NodeShape.MetaList] = { + if (Utility.isListNode(tail)) { + val node = CoreDef.Node.addRefined[NodeShape.MetaList] + + val headLink = Link.New.Connected(node, head) + val tailLink = Link.New.Connected(node, tail) + + node.head = headLink + node.tail = tailLink + node.location = Constants.invalidLocation + node.parents = Vector() + + Right(node) + } else { + val errorElems = Utility.coreListFrom(tail) + val errorNode = ConstructionError(errorElems, tail.location) + + Left(errorNode) + } + } + + /** Creates a representation of the end of a linked-list on the core + * graph. + * + * This should _only_ be used in conjunction with [[NodeShape.MetaList]]. + * + * The location contained in this node is _invalid_ as it does not + * represent any location in the program source. + * + * @param core an implicit instance of core + * @return a node representing the end of an on-graph meta list + */ + def MetaNil()(implicit core: Core): RefinedNode[NodeShape.MetaNil] = { + val node = CoreDef.Node.addRefined[NodeShape.MetaNil] + + node.location = Constants.invalidLocation + node.parents = Vector() + + node + } + + /** Creates a representation of a meta-value `true` in the core graph. + * + * The location contained in this node is _invalid_ as it does not + * represent any location in the program source. + * + * @param core an implicit instance of core + * @return a node representing the on-graph metavalue `true` + */ + def MetaTrue()(implicit core: Core): RefinedNode[NodeShape.MetaTrue] = { + val node = CoreDef.Node.addRefined[NodeShape.MetaTrue] + + node.location = Constants.invalidLocation + node.parents = Vector() + + node + } + + /** Creates a representation of the meta-value `false` in the core graph. + * + * The location contained in this node is _invalid_ as it does not + * represent any location in the program source. + * + * @param core an implicit instance of core + * @return a node representing the on-graph metavalue `false` + */ + def MetaFalse()(implicit core: Core): RefinedNode[NodeShape.MetaFalse] = { + val node = CoreDef.Node.addRefined[NodeShape.MetaFalse] + + node.location = Constants.invalidLocation + node.parents = Vector() + + node + } + + // === Literals ========================================================= + + /** Creates a node containing a numeric literal. + * + * @param number the literal number + * @param location the source location for the literal + * @param core an implicit instance of core + * @return a numeric literal node representing [[number]] + */ + def NumericLiteral( + number: String, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.NumericLiteral] = { + val node = CoreDef.Node.addRefined[NodeShape.NumericLiteral] + + node.number = number + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node containing a textual literal. + * + * @param text the literal text + * @param location the source location for the literal + * @param core an implicit instance of core + * @return a textual literal node representing [[text]] + */ + def TextLiteral( + text: String, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.TextLiteral] = { + val node = CoreDef.Node.addRefined[NodeShape.TextLiteral] + + node.text = text + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node containing a foreign code literal. + * + * @param code the foreign code + * @param location the source location for the literal + * @param core an implicit instance of core + * @return a foreign code literal node representing [[code]] + */ + def ForeignCodeLiteral( + code: String, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.ForeignCodeLiteral] = { + val node = CoreDef.Node.addRefined[NodeShape.ForeignCodeLiteral] + + node.code = code + node.location = location + node.parents = Vector() + + node + } + + // === Names ============================================================ + + /** Creates a node representing a name. + * + * @param nameLiteral the literal representation of the name + * @param location the source location for the name + * @param core an implicit instance of core + * @return a node representing the name [[nameLiteral]] + */ + def Name( + nameLiteral: String, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.Name] = { + val node = CoreDef.Node.addRefined[NodeShape.Name] + + node.nameLiteral = nameLiteral + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing a usage of `this`. + * + * @param location the source location of the `this` usage + * @param core an implicit instance of core + * @return a node representing the `this` usage at [[location]] + */ + def ThisName( + location: Location + )(implicit core: Core): RefinedNode[NodeShape.ThisName] = { + val node = CoreDef.Node.addRefined[NodeShape.ThisName] + + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing a usage of `here`. + * + * @param location the source location of the `here` usage + * @param core an implicit instance of core + * @return a node representing the `here` usage at [[location]] + */ + def HereName( + location: Location + )(implicit core: Core): RefinedNode[NodeShape.HereName] = { + val node = CoreDef.Node.addRefined[NodeShape.HereName] + + node.location = location + node.parents = Vector() + + node + } + + // === Module =========================================================== + + /** Creates a node representing a module definition. + * + * @param name the name of the module + * @param imports the list of imports for the module, as a valid meta + * list + * @param definitions the list of definitions in the module, as a valid + * meta list + * @param location the source location of the module definition + * @param core an implicit instance of core + * @return a node representing the module definition + */ + def ModuleDef( + name: Node, + imports: Node, + definitions: Node, + location: Location + )(implicit core: Core): ConsErrOr[NodeShape.ModuleDef] = { + if (!Utility.isListNode(imports)) { + val errorElems = Utility.coreListFrom(imports) + val error = ConstructionError(errorElems, imports.location) + + Left(error) + } else if (!Utility.isListNode(definitions)) { + val errorElems = Utility.coreListFrom(definitions) + val error = ConstructionError(errorElems, definitions.location) + + Left(error) + } else { + val node = CoreDef.Node.addRefined[NodeShape.ModuleDef] + + val nameLink = Link.New.Connected(node, name) + val importsLink = Link.New.Connected(node, imports) + val definitionsLink = Link.New.Connected(node, definitions) + + node.name = nameLink + node.imports = importsLink + node.definitions = definitionsLink + node.location = location + node.parents = Vector() + + Right(node) + } + } + + /** Creates a node representing an import statement. + * + * @param segments the segments of the import path, as a valid meta list + * @param location the source location of the import statement + * @param core an implicit instance of core + * @return a node representing the import statement + */ + def Import( + segments: Node, + location: Location + )(implicit core: Core): ConsErrOr[NodeShape.Import] = { + if (Utility.isListNode(segments)) { + val node = CoreDef.Node.addRefined[NodeShape.Import] + + val segmentsLink = Link.New.Connected(node, segments) + + node.segments = segmentsLink + node.location = location + node.parents = Vector() + + Right(node) + } else { + val errList = Utility.coreListFrom(segments) + val errNode = ConstructionError(errList, segments.location) + + Left(errNode) + } + } + + /** Creates a node representing a top-level binding. + * + * This node does not represent the binding itself, but only serves to + * represent the connection between the binding and its containing + * module. + * + * @param module the module in which [[Binding]] is defined + * @param binding the binding itself + * @param location the source location of the binding + * @param core an implicit instance of core + * @return a node representing the top-level binding + */ + def TopLevelBinding( + module: Node, + binding: Node, + location: Location + )(implicit core: Core): ConsErrOr[NodeShape.TopLevelBinding] = { + binding match { + case NodeShape.Binding.any(_) => + val node = CoreDef.Node.addRefined[NodeShape.TopLevelBinding] + + val moduleLink = Link.New.Connected(node, module) + val bindingLink = Link.New.Connected(node, binding) + + node.module = moduleLink + node.binding = bindingLink + node.location = location + node.parents = Vector() + + Right(node) + case _ => + val errNode = ConstructionError(binding, binding.location) + + Left(errNode) + } + } + + // === Type Definitions ================================================= + + /** Creates a node representing an atom definition. + * + * @param name the atom's name + * @param args the atom's arguments + * @param location the source location of the atom + * @param core an implicit instance of core + * @return a node representing an atom definition for [[Name]] + */ + def AtomDef( + name: Node, + args: Node, + location: Location + )(implicit core: Core): ConsErrOr[NodeShape.AtomDef] = { + if (Utility.isListNode(args)) { + val node = CoreDef.Node.addRefined[NodeShape.AtomDef] + + val nameLink = Link.New.Connected(node, name) + val argsLink = Link.New.Connected(node, args) + + node.name = nameLink + node.args = argsLink + node.location = location + node.parents = Vector() + + Right(node) + } else { + val errList = Utility.coreListFrom(args) + val errNode = ConstructionError(errList, args.location) + + Left(errNode) + } + } + + /** Creates a node representing a complex type definition. + * + * @param name the name of the type definition + * @param typeParams the type parameters + * @param body the body of the definition + * @param location the source location of the definition + * @param core an implicit instance of core + * @return a node representing the type definition for [[Name]] + */ + def TypeDef( + name: Node, + typeParams: Node, + body: Node, + location: Location + )(implicit core: Core): ConsErrOr[NodeShape.TypeDef] = { + if (!Utility.isListNode(typeParams)) { + val errList = Utility.coreListFrom(typeParams) + val errNode = ConstructionError(errList, typeParams.location) + + Left(errNode) + } else if (!Utility.isListNode(body)) { + val errList = Utility.coreListFrom(body) + val errNode = ConstructionError(errList, body.location) + + Left(errNode) + } else { + val node = CoreDef.Node.addRefined[NodeShape.TypeDef] + + val nameLink = Link.New.Connected(node, name) + val typeParamsLink = Link.New.Connected(node, typeParams) + val bodyLink = Link.New.Connected(node, body) + + node.name = nameLink + node.typeParams = typeParamsLink + node.body = bodyLink + node.location = location + node.parents = Vector() + + Right(node) + } + } + + // === Typing =========================================================== + + /** Creates a node representing the ascription of a type to a value. + * + * The signature is an entirely arbitrary Enso expression, as required by + * the language's syntactic unification. + * + * @param typed the expression being ascribed a type + * @param sig the type being ascribed to [[typed]] + * @param location the source location of the ascription + * @param core an implicit instance of core + * @return a node representing the ascription of the type represented by + * [[sig]] to [[typed]] + */ + def TypeAscription( + typed: Node, + sig: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.TypeAscription] = { + val node = CoreDef.Node.addRefined[NodeShape.TypeAscription] + + val typedLink = Link.New.Connected(node, typed) + val sigLink = Link.New.Connected(node, sig) + + node.typed = typedLink + node.sig = sigLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing the ascription of a monadic context to a + * value (using the `in` keyword). + * + * @param typed the expression being ascribed a context + * @param context the context being ascribed to [[typed]] + * @param location the source location of the ascription + * @param core an implicit instance of core + * @return a node representing the ascription of the context [[context]] + * to the expression [[typed]] + */ + def ContextAscription( + typed: Node, + context: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.ContextAscription] = { + val node = CoreDef.Node.addRefined[NodeShape.ContextAscription] + + val typedLink = Link.New.Connected(node, typed) + val contextLink = Link.New.Connected(node, context) + + node.typed = typedLink + node.context = contextLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing a typeset member. + * + * At most two of [[label]], [[memberType]] and [[value]] may be + * [[NodeShape.Empty]]. + * + * @param label the label of the member, if provided + * @param memberType the type of the member, if provided + * @param value the value of the member, if provided + * @param location the source location of the member definition + * @param core an implicit instance of core + * @return a node representing a typeset member called [[label]] with + * type [[memberType]] and default value [[value]] + */ + def TypesetMember( + label: Node, + memberType: Node, + value: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.TypesetMember] = { + val node = CoreDef.Node.addRefined[NodeShape.TypesetMember] + + val labelLink = Link.New.Connected(node, label) + val memberTypeLink = Link.New.Connected(node, memberType) + val valueLink = Link.New.Connected(node, value) + + node.label = labelLink + node.memberType = memberTypeLink + node.value = valueLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing the typeset subsumption operator `<:`. + * + * This construct does not represent a user-facing language element at + * this time. + * + * @param left the left operand + * @param right the right operand + * @param location the location in the source to which the operator + * corresponds + * @param core an implicit instance of core + * @return a node representing the judgement that [[left]] `<:` [[right]] + */ + def TypesetSubsumption( + left: Node, + right: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.TypesetSubsumption] = { + val node = CoreDef.Node.addRefined[NodeShape.TypesetSubsumption] + + val leftLink = Link.New.Connected(node, left) + val rightLink = Link.New.Connected(node, right) + + node.left = leftLink + node.right = rightLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing the typeset equality operator `~`. + * + * This construct does not represent a user-facing language element at + * this time. + * + * @param left the left operand + * @param right the right operand + * @param location the location in the source to which the operator + * corresponds + * @param core an implicit instance of core + * @return a node representing the judgement that [[left]] `~` [[right]] + */ + def TypesetEquality( + left: Node, + right: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.TypesetEquality] = { + val node = CoreDef.Node.addRefined[NodeShape.TypesetEquality] + + val leftLink = Link.New.Connected(node, left) + val rightLink = Link.New.Connected(node, right) + + node.left = leftLink + node.right = rightLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing the typeset concatenation operator `,`. + * + * @param left the left operand + * @param right the right operand + * @param location the location in the source to which the operator + * corresponds + * @param core an implicit instance of core + * @return a node representing the judgement of [[left]] `,` [[right]] + */ + def TypesetConcat( + left: Node, + right: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.TypesetConcat] = { + val node = CoreDef.Node.addRefined[NodeShape.TypesetConcat] + + val leftLink = Link.New.Connected(node, left) + val rightLink = Link.New.Connected(node, right) + + node.left = leftLink + node.right = rightLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing the typeset union operator `|`. + * + * @param left the left operand + * @param right the right operand + * @param location the location in the source to which the operator + * corresponds + * @param core an implicit instance of core + * @return a node representing the judgement of [[left]] `|` [[right]] + */ + def TypesetUnion( + left: Node, + right: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.TypesetUnion] = { + val node = CoreDef.Node.addRefined[NodeShape.TypesetUnion] + + val leftLink = Link.New.Connected(node, left) + val rightLink = Link.New.Connected(node, right) + + node.left = leftLink + node.right = rightLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing the typeset intersection operator `&`. + * + * @param left the left operand + * @param right the right operand + * @param location the location in the source to which the operator + * corresponds + * @param core an implicit instance of core + * @return a node representing the judgement of [[left]] `&` [[right]] + */ + def TypesetIntersection( + left: Node, + right: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.TypesetIntersection] = { + val node = CoreDef.Node.addRefined[NodeShape.TypesetIntersection] + + val leftLink = Link.New.Connected(node, left) + val rightLink = Link.New.Connected(node, right) + + node.left = leftLink + node.right = rightLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing the typeset subtraction operator `\`. + * + * @param left the left operand + * @param right the right operand + * @param location the location in the source to which the operator + * corresponds + * @param core an implicit instance of core + * @return a node representing the judgement of [[left]] `\` [[right]] + */ + def TypesetSubtraction( + left: Node, + right: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.TypesetSubtraction] = { + val node = CoreDef.Node.addRefined[NodeShape.TypesetSubtraction] + + val leftLink = Link.New.Connected(node, left) + val rightLink = Link.New.Connected(node, right) + + node.left = leftLink + node.right = rightLink + node.location = location + node.parents = Vector() + + node + } + + // === Function ========================================================= + + /** Creates a node representing a lambda expression, the `->` function + * arrow. + * + * Please note that all lambdas in Enso are explicitly single-argument. + * + * @param arg the argument to the lambda + * @param body the body of the lambda + * @param location the location of this node in the program source + * @param core an implicit instance of core + * @return a lambda node with [[arg]] and [[body]] as its children + */ + def Lambda( + arg: Node, + body: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.Lambda] = { + val node = CoreDef.Node.addRefined[NodeShape.Lambda] + + val argLink = Link.New.Connected(node, arg) + val bodyLink = Link.New.Connected(node, body) + + node.arg = argLink + node.body = bodyLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing a function definition. + * + * @param name the name of the function being defined + * @param args the arguments to the function being defined + * @param body the body of the function being defined + * @param location the source location of the function definition + * @param core an implicit instance of core + * @return a node representing a function defined for [[Name]] + */ + def FunctionDef( + name: Node, + args: Node, + body: Node, + location: Location + )(implicit core: Core): ConsErrOr[NodeShape.FunctionDef] = { + if (Utility.isListNode(args)) { + val node = CoreDef.Node.addRefined[NodeShape.FunctionDef] + + val nameLink = Link.New.Connected(node, name) + val argsLink = Link.New.Connected(node, args) + val bodyLink = Link.New.Connected(node, body) + + node.name = nameLink + node.args = argsLink + node.body = bodyLink + node.location = location + node.parents = Vector() + + Right(node) + } else { + val errList = Utility.coreListFrom(args) + val errNode = ConstructionError(errList, args.location) + + Left(errNode) + } + } + + /** Creates a node representing a method definition + * + * @param targetPath the method path for the definition + * @param name the method name + * @param function the implementation of the method. This must either be + * a [[NodeShape.Lambda]] or a [[NodeShape.FunctionDef]] + * @param location the source location of the method definition + * @param core an implicit instance of core + * @return a node that defines method [[Name]] on [[path]] + */ + def MethodDef( + targetPath: Node, + name: Node, + function: Node, + location: Location + )(implicit core: Core): ConsErrOr[NodeShape.MethodDef] = { + val bodyIsValid = function match { + case NodeShape.FunctionDef.any(_) => true + case NodeShape.Lambda.any(_) => true + case _ => false + } + + if (bodyIsValid) { + val node = CoreDef.Node.addRefined[NodeShape.MethodDef] + + val targetPathLink = Link.New.Connected(node, targetPath) + val nameLink = Link.New.Connected(node, name) + val functionLink = Link.New.Connected(node, function) + + node.targetPath = targetPathLink + node.name = nameLink + node.function = functionLink + node.location = location + node.parents = Vector() + + Right(node) + } else { + val errList = Utility.coreListFrom(function) + val errNode = ConstructionError(errList, function.location) + + Left(errNode) + } + } + + // === Definition-Site Argument Types =================================== + + /** Creates a node representing an ignored argument. + * + * An ignored argument is one that is not used in the body and is + * explicitly ignored so as not to introduce warnings. + * + * @param location the location of the ignored argument usage + * @param core an implicit instance of core + * @return a node representing an ignored argument + */ + def IgnoredArgument( + location: Location + )(implicit core: Core): RefinedNode[NodeShape.IgnoredArgument] = { + val node = CoreDef.Node.addRefined[NodeShape.IgnoredArgument] + + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing an argument from a function definition + * site. + * + * @param name the name of the argument + * @param suspended whether or not the argument is suspended, as either + * [[NodeShape.MetaTrue]] or [[NodeShape.MetaFalse]] + * @param default the default value for the argument, if present + * @param location the source location of the argument + * @param core an implicit instance of core + * @return a node representing a definition site argument called [[Name]] + */ + def DefinitionArgument( + name: Node, + suspended: Node, + default: Node, + location: Location + )(implicit core: Core): ConsErrOr[NodeShape.DefinitionArgument] = { + if (Utility.isBoolNode(suspended)) { + val node = CoreDef.Node.addRefined[NodeShape.DefinitionArgument] + + val nameLink = Link.New.Connected(node, name) + val suspendedLink = Link.New.Connected(node, suspended) + val defaultLink = Link.New.Connected(node, default) + + node.name = nameLink + node.suspended = suspendedLink + node.default = defaultLink + node.location = location + node.parents = Vector() + + Right(node) + } else { + val errList = Utility.coreListFrom(suspended) + val errNode = ConstructionError(errList, suspended.location) + + Left(errNode) + } + } + + // === Applications ===================================================== + + /** Creates a node representing a function application. + * + * Please note that _all_ functions in Enso are curried by default. and + * applications to multiple arguments are represented in [[Core]] as + * single-argument functions. + * + * @param function the function being applied + * @param argument the argument to [[function]] + * @param location the soure location for the application + * @param core an implicit instance of core + * @return a node that applies [[function]] to [[argument]] + */ + def Application( + function: Node, + argument: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.Application] = { + val node = CoreDef.Node.addRefined[NodeShape.Application] + + val functionLink = Link.New.Connected(node, function) + val argumentLink = Link.New.Connected(node, argument) + + node.function = functionLink + node.argument = argumentLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing an infix application. + * + * @param left the left argument to the operator + * @param operator the operator being applied + * @param right the right argument to the operator + * @param location the source location of the infox application + * @param core an implicit instance of core + * @return a node representing the application of [[operator]] to + * [[left]] and [[right]] + */ + def InfixApplication( + left: Node, + operator: Node, + right: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.InfixApplication] = { + val node = CoreDef.Node.addRefined[NodeShape.InfixApplication] + + val leftLink = Link.New.Connected(node, left) + val operatorLink = Link.New.Connected(node, operator) + val rightLink = Link.New.Connected(node, right) + + node.left = leftLink + node.operator = operatorLink + node.right = rightLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing a left operator section. + * + * @param arg the left argument to [[operator]] + * @param operator the function being applied + * @param location the source location of the application + * @param core an implicit instance of core + * @return a node representing the partial application of [[operator]] to + * [[arg]] + */ + def LeftSection( + arg: Node, + operator: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.LeftSection] = { + val node = CoreDef.Node.addRefined[NodeShape.LeftSection] + + val argLink = Link.New.Connected(node, arg) + val operatorLink = Link.New.Connected(node, operator) + + node.arg = argLink + node.operator = operatorLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing a right operator section. + * + * @param operator the function being applied + * @param arg the right argument to [[operator]] + * @param location the source location of the application + * @param core an implicit instance of core + * @return a node representing the partial application of [[operator]] to + * [[arg]] + */ + def RightSection( + operator: Node, + arg: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.RightSection] = { + val node = CoreDef.Node.addRefined[NodeShape.RightSection] + + val operatorLink = Link.New.Connected(node, operator) + val argLink = Link.New.Connected(node, arg) + + node.operator = operatorLink + node.arg = argLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing a centre operator section. + * + * @param operator the function being partially applied + * @param location the source location of the application + * @param core an implicit instance of core + * @return a node representing the partial application of [[operator]] + */ + def CentreSection( + operator: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.CentreSection] = { + val node = CoreDef.Node.addRefined[NodeShape.CentreSection] + + val operatorLink = Link.New.Connected(node, operator) + + node.operator = operatorLink + node.location = location + node.parents = Vector() + + node + } + + /** A node representing a term being explicitly forced. + * + * An explicitly forced term is one where the user has explicitly called + * the `force` operator on it. This is useful only while the compiler + * does not _automatically_ handle suspensions and forcing. + * + * PLEASE NOTE: This is temporary and will be removed as soon as the + * compiler is capable enough to not require it. + * + * @param expression the expression being forced + * @param location the source location of the forced expression + * @param core an implicit instance of core + * @return a node representing [[expression]] being explicitly forced + */ + def ForcedTerm( + expression: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.ForcedTerm] = { + val node = CoreDef.Node.addRefined[NodeShape.ForcedTerm] + + val expressionLink = Link.New.Connected(node, expression) + + node.expression = expressionLink + node.location = location + node.parents = Vector() + + node + } + + // === Call-Site Argument Types ========================================= + + /** Creates a node representing a lambda shorthand argument. + * + * A lambda shorthand argument is the name for the usage of `_` at a + * function call-site, where it is syntax sugar for a lambda parameter to + * that function. + * + * @param location the location of this argument in the source code + * @param core an implicit instance of core + * @return a node representing the `_` argument found at [[location]] + */ + def LambdaShorthandArgument( + location: Location + )(implicit core: Core): RefinedNode[NodeShape.LambdaShorthandArgument] = { + val node = CoreDef.Node.addRefined[NodeShape.LambdaShorthandArgument] + + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing an argument from a function call site. + * + * The expression must always be present, but the argument [[Name]] may + * be an instance of [[NodeShape.Empty]]. + * + * @param expression the expression being passes as the argument + * @param name the name of the argument, if present + * @param location the source location of this argument + * @param core an implicit instance of core + * @return a node representing the use of [[expression]] as an argument + * to a function + */ + def CallSiteArgument( + expression: Node, + name: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.CallSiteArgument] = { + val node = CoreDef.Node.addRefined[NodeShape.CallSiteArgument] + + val expressionLink = Link.New.Connected(node, expression) + val nameLink = Link.New.Connected(node, name) + + node.expression = expressionLink + node.name = nameLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing a usage of the function defaults + * suspension operator `...`. + * + * @param location the source location of the operator usage + * @param core an implicit instance of core + * @return a node representing a usage of the `...` operator + */ + def SuspendDefaultsOperator( + location: Location + )(implicit core: Core): RefinedNode[NodeShape.SuspendDefaultsOperator] = { + val node = CoreDef.Node.addRefined[NodeShape.SuspendDefaultsOperator] + + node.location = location + node.parents = Vector() + + node + } + + // === Structure ======================================================== + + /** Creates a node representing a block expression. + * + * @param expressions a valid meta list of expressions (should be + * [[NodeShape.MetaNil]] if none are present + * @param returnVal the final expression in the block + * @param location the source location of the block + * @param core an implicit instance of core + * @return a representation of a block containing [[expressions]] and + * [[returnVal]] + */ + def Block( + expressions: Node, + returnVal: Node, + location: Location + )(implicit core: Core): ConsErrOr[NodeShape.Block] = { + if (Utility.isListNode(expressions)) { + val node = CoreDef.Node.addRefined[NodeShape.Block] + + val expressionsLink = Link.New.Connected(node, expressions) + val returnValLink = Link.New.Connected(node, returnVal) + + node.expressions = expressionsLink + node.returnVal = returnValLink + node.location = location + node.parents = Vector() + + Right(node) + } else { + val errList = Utility.coreListFrom(expressions) + val errNode = ConstructionError(errList, expressions.location) + + Left(errNode) + } + } + + /** Creates a node representing a binding of the form `name = expression`. + * + * @param name the name being bound to + * @param expression the expression being bound to [[Name]] + * @param location the source location of the binding + * @param core an implicit instance of core + * @return a representation of the binding of the result of + * [[expression]] to [[Name]] + */ + def Binding( + name: Node, + expression: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.Binding] = { + val node = CoreDef.Node.addRefined[NodeShape.Binding] + + val nameLink = Link.New.Connected(node, name) + val expressionLink = Link.New.Connected(node, expression) + + node.name = nameLink + node.expression = expressionLink + node.location = location + node.parents = Vector() + + node + } + + // === Case Expression ================================================== + + /** Creates a node representing a case expression. + * + * @param scrutinee the expression being matched on + * @param branches the branches doing the matching + * @param location the soure location of the case expression + * @param core an implicit instance of core + * @return a node representing pattern matching on [[scrutinee]] using + * [[branches]] + */ + def CaseExpr( + scrutinee: Node, + branches: Node, + location: Location + )(implicit core: Core): ConsErrOr[NodeShape.CaseExpr] = { + if (Utility.isListNode(branches)) { + val node = CoreDef.Node.addRefined[NodeShape.CaseExpr] + + val scrutineeLink = Link.New.Connected(node, scrutinee) + val branchesLink = Link.New.Connected(node, branches) + + node.scrutinee = scrutineeLink + node.branches = branchesLink + node.location = location + node.parents = Vector() + + Right(node) + } else { + val errList = Utility.coreListFrom(branches) + val errNode = ConstructionError(errList, branches.location) + + Left(errNode) + } + } + + /** Creates a node representing a branch in a case expression. + * + * @param pattern the pattern match for the branch + * @param expression the expression that is executed if [[pattern]] + * successfully matches the case scrutinee + * @param location the source location of the case branch + * @param core an implicit instance of core + * @return a node representing a case branch matching [[pattern]] + */ + def CaseBranch( + pattern: Node, + expression: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.CaseBranch] = { + val node = CoreDef.Node.addRefined[NodeShape.CaseBranch] + + val patternLink = Link.New.Connected(node, pattern) + val expressionLink = Link.New.Connected(node, expression) + + node.pattern = patternLink + node.expression = expressionLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing a structural pattern. + * + * A structural pattern is one that examines the _structure_ of the + * scrutinee. However, as Enso is a dependently typed language, an + * examination of the structure is also an examination of the type. + * + * @param matchExpression the expression representing the pattern + * @param location the source location of the patttern + * @param core an implicit instance of core + * @return a node representing the structural match defined by + * [[matchExpression]] + */ + def StructuralPattern( + matchExpression: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.StructuralPattern] = { + val node = CoreDef.Node.addRefined[NodeShape.StructuralPattern] + + val matchExpressionLink = Link.New.Connected(node, matchExpression) + + CoreDef.Node.addParent(matchExpression, matchExpressionLink) + + node.matchExpression = matchExpressionLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing a type-based pattern. + * + * A type-based pattern is one that purely examines the type of the + * scrutinee, without including any structural elements. + * + * @param matchExpression the expression representing the pattern + * @param location the source location of the pattern + * @param core an implicit instance of core + * @return a node representing the type-based match defined by + * [[matchExpression]] + */ + def TypePattern( + matchExpression: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.TypePattern] = { + val node = CoreDef.Node.addRefined[NodeShape.TypePattern] + + val matchExpressionLink = Link.New.Connected(node, matchExpression) + + node.matchExpression = matchExpressionLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing a named pattern. + * + * A named pattern is one that renames the scrutinee in the pattern + * branch. + * + * @param matchExpression the expression representing the pattern + * @param location the soure location of the pattern + * @param core an implicit instance of core + * @return a node representing the type-based match defined by + * [[matchExpression]] + */ + def NamedPattern( + matchExpression: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.NamedPattern] = { + val node = CoreDef.Node.addRefined[NodeShape.NamedPattern] + + val matchExpressionLink = Link.New.Connected(node, matchExpression) + + node.matchExpression = matchExpressionLink + node.location = location + node.parents = Vector() + + node + } + + /** Creates a node representing a fallback pattern. + * + * A fallback pattern is also known as a catch-all, pattern, and will + * unconditionally match any scrutinee. + * + * @param location the soure location of the pattern + * @param core an implicit instance of core + * @return a node representing the fallback pattern at [[location]] + */ + def FallbackPattern( + location: Location + )(implicit core: Core): RefinedNode[NodeShape.FallbackPattern] = { + val node = CoreDef.Node.addRefined[NodeShape.FallbackPattern] + + node.location = location + node.parents = Vector() + + node + } + + // === Comments ========================================================= + + /** Creates a node representing an entity with an associated doc comment. + * + * @param commented the entity that has the comment + * @param doc the documentation associated with [[commented]] + * @param location the source location of [[commented]] and its + * associated doc + * @param core an implicit instance of core + * @return a node representing the association of [[doc]] to + * [[commented]] + */ + def DocComment( + commented: Node, + doc: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.DocComment] = { + val node = CoreDef.Node.addRefined[NodeShape.DocComment] + + val commentedLink = Link.New.Connected(node, commented) + val docLink = Link.New.Connected(node, doc) + + node.commented = commentedLink + node.doc = docLink + node.location = location + node.parents = Vector() + + node + } + + // === Foreign ========================================================== + + /** Creates a node representing a block of foreign code. + * + * @param language the programming language for which [[code]] is written + * @param code the source code in [[language]] (must be a + * [[NodeShape.ForeignCodeLiteral]]) + * @param location the source location of the foreign code block + * @param core an implicit instance of core + * @return a node representing [[code]] in [[language]] + */ + def ForeignDefinition( + language: Node, + code: Node, + location: Location + )(implicit core: Core): ConsErrOr[NodeShape.ForeignDefinition] = { + code match { + case NodeShape.ForeignCodeLiteral.any(_) => + val node = CoreDef.Node.addRefined[NodeShape.ForeignDefinition] + + val languageLink = Link.New.Connected(node, language) + val codeLink = Link.New.Connected(node, code) + + node.language = languageLink + node.code = codeLink + node.location = location + node.parents = Vector() + + Right(node) + case _ => + val errList = Utility.coreListFrom(code) + val errNode = ConstructionError(errList, code.location) + + Left(errNode) + } + } + + // === Errors =========================================================== + + /** Creates a node representing a syntax error. + * + * @param errorAst the AST that is syntactically invalid + * @param core an implicit instance of core + * @return a node representing the syntax error described by [[errorAst]] + */ + def SyntaxError( + errorAst: AST + )(implicit core: Core): RefinedNode[NodeShape.SyntaxError] = { + val node = CoreDef.Node.addRefined[NodeShape.SyntaxError] + val errLocation: Location = + errorAst.location + .map(Conversions.astLocationToNodeLocation) + .getOrElse(Constants.invalidLocation) + + node.errorAst = errorAst + node.location = errLocation + node.parents = Vector() + + node + } + + /** Creates a node representing an error that occurred when constructing + * a [[Core]] expression. + * + * @param erroneousCore the core expression(s) that caused a problem (may + * be passed as a meta list) + * @param location the location at which the erroneous core occurred + * @param core an implicit instance of core + * @return a node representing an erroneous core expression + */ + def ConstructionError( + erroneousCore: Node, + location: Location + )(implicit core: Core): RefinedNode[NodeShape.ConstructionError] = { + val erroneousCoreList: Node = if (Utility.isListNode(erroneousCore)) { + erroneousCore + } else { + Utility.coreListFrom(erroneousCore) + } + + val node = CoreDef.Node.addRefined[NodeShape.ConstructionError] + + val erroneousCoreLink = + Link.New.Connected(node, erroneousCoreList) + + node.erroneousCore = erroneousCoreLink + node.location = location + node.parents = Vector() + + node + } + } + + /** Useful conversions between types that are used for Core nodes. */ + object Conversions { + + /** Converts the parser's location representation into Core's location + * representation. + * + * @param location a location from the parser + * @return the core representation of [[location]] + */ + implicit def astLocationToNodeLocation( + location: AstLocation + ): Location = LocationVal(location.start, location.end) + } + + /** Constants for working with nodes. */ + object Constants { + + /** An invalid location in the program source. */ + val invalidSourceIndex: Int = -1 + val invalidLocation: Location = + LocationVal(invalidSourceIndex, invalidSourceIndex) + } + + /** Utility functions for working with nodes. */ + object Utility { + + /** Checks if two lists on the core graph are equal. + * + * Equality for lists is defined as the lists containing the same nodes + * as members. The nodes making up the lists themselves need not be + * equal. + * + * @param left the first list + * @param right the second list + * @param core an implicit instance of core + * @return `true` if [[left]] is equal to [[right]], `false` otherwise + */ + def listsAreEqual( + left: RefinedNode[MetaList], + right: RefinedNode[MetaList] + )(implicit core: Core): Boolean = { + @tailrec + def go( + left: Node, + right: Node + ): Boolean = { + left match { + case NodeShape.MetaNil.any(_) => + right match { + case NodeShape.MetaNil.any(_) => true + case _ => false + } + case NodeShape.MetaList.any(left1) => + right match { + case NodeShape.MetaList.any(right1) => + (left1.head.target == right1.head.target) && go( + left1.tail.target, + right1.tail.target + ) + case _ => false + } + case _ => false + } + } + + go(left, right) + } + + /** Checks if the provided node is a meta-level boolean node. + * + * @param node the node to check + * @param core an implicit instance of core + * @return `true` if [[node]] is a meta boolean, `false` otherwise + */ + def isBoolNode(node: Node)(implicit core: Core): Boolean = { + node match { + case NodeShape.MetaTrue.any(_) => true + case NodeShape.MetaFalse.any(_) => true + case _ => false + } + } + + /** Checks if the provided node is a meta-level list node. + * + * A node is considered to be a list node when it has either the shape + * [[NodeShape.MetaList]] or the shape [[NodeShape.MetaNil]]. + * + * @param node the node to check + * @param core an implicit instance of core + * @return `true` if [[node]] is a list node, otherwise `false` + */ + def isListNode(node: Node)(implicit core: Core): Boolean = { + node match { + case NodeShape.MetaNil.any(_) => true + case NodeShape.MetaList.any(_) => true + case _ => false + } + } + + /** Constructs a meta list on the [[Core]] graph from a single core + * expression. + * + * @param node the expression to turn into a valid list + * @param core an implicit instance of core + * @return a node representing the head of a meta-level list + */ + def coreListFrom( + node: Node + )(implicit core: Core): RefinedNode[NodeShape.MetaList] = { + coreListFrom(NonEmptyList(node, List())) + } + + /** Constructs a meta list on the [[Core]] graph from a list of [[Core]] + * nodes. + * + * @param nodes the nodes to make a list out of + * @param core an implicit instance of core + * @return a node representing the head of a meta-level list + */ + def coreListFrom( + nodes: NonEmptyList[Node] + )(implicit core: Core): RefinedNode[NodeShape.MetaList] = { + val nodesWithNil = nodes :+ New.MetaNil().wrapped + + // Note [Unsafety in List Construction] + val unrefinedMetaList = + nodesWithNil.toList.reduceRight( + (l, r) => New.MetaList(l, r).right.get.wrapped + ) + + PrimGraph.Component.Refined + .wrap[NodeShape, NodeShape.MetaList, Node](unrefinedMetaList) + } + + /* Note [Unsafety in List Construction] + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * This makes use of internal implementation details to know that calling + * `right` here is always safe. The error condition for the `metaList` + * constructor occurs when the `tail` argument doesn't point to a valid + * list element, but here we construct that element directly and hence we + * know that it is valid. + * + * Furthermore, we can unconditionally refine the type as we know that the + * node we get back must be a MetaList, and we know that the input is not + * empty. + * + * It also bears noting that for this to be safe we _must_ use a right + * reduce, rather than a left reduce, otherwise the elements will not be + * constructed properly. This does, however, mean that this can stack + * overflow when provided with too many elements. + */ + } + } + + // ========================================================================== + // === Link ================================================================= + // ========================================================================== + + /** Functionality for working with links. */ + object Link { + object New { + + /** Makes a link with the provided source and target. + * + * @param source the start of the link + * @param target the end of the link + * @param core an implicit instance of core + * @return a link from [[source]] to [[target]] + */ + def Connected(source: Node, target: Node)(implicit core: Core): Link = { + val link = core.graph.addLink() + + link.source = source + link.target = target + + CoreDef.Node.addParent(target, link) + + link + } + + /** Makes a link with only a source. + * + * The target is defaulted to a new empty node. + * + * @param source the start of the link + * @param core an implicit instance of core + * @return a link from [[source]] to a new [[NodeShape.Empty]] node + */ + def Disconnected(source: Node)(implicit core: Core): Link = { + val link = core.graph.addLink() + val emptyNode = Node.New.Empty() + + link.source = source + link.target = emptyNode + + CoreDef.Node.addParent(emptyNode, link) + + link + } + } + } + // ========================================================================== + // === Implicits ============================================================ + // ========================================================================== + + /* Note [Implicit Conversions On Implicits] + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * While the enforced usage of the compiler plugin 'splain' does much to make + * debugging issues with implicit resolution easier, implicit conversions of + * implicit values can sometimes fail to resolve. If you have all of the + * imports described in the `Core` doc comment, as well as an implicit value + * of type `Core` and are seeing errors related to implicits resolution, you + * may be running into issues with implicits resolution. + * + * A quick fix is to make the values that the following implicits would + * generate _explicitly_ available as implicits in the usage scope. + */ + + /** Implicitly converts an implicit instance of core to the underlying graph + * data. + * + * @param core the core instance to convert + * @return the graph data stored in [[core]] + */ + implicit def getGraphData(implicit core: Core): GraphData = core.graph + + /** Implicitly converts an implicit instance of core to the associated storage + * for literals. + * + * @param core the core instance to convert + * @return the literal storage stored in [[core]] + */ + implicit def getLiteralStorage(implicit core: Core): LiteralStorage = + core.literalStorage + + /** Implicitly converts an implicit instance of core to the associated storage + * for names. + * + * @param core the core instance to convert + * @return the name storage stored in [[core]] + */ + implicit def getNameStorage(implicit core: Core): NameStorage = + core.nameStorage + + /** Implicitly converts an implicit instance of core to the associated storage + * for parent links. + * + * @param core the core instance to convert + * @return the parent link storage stored in [[core]] + */ + implicit def getParentStorage(implicit core: Core): ParentStorage = + core.parentStorage + + /** Implicitly converts an implicit instance of core to the associated storage + * for ast data. + * + * @param core the core instance to convert + * @return the ast storage stored in [[core]] + */ + implicit def getAstStorage(implicit core: Core): AstStorage = + core.astStorage + +} diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/core/CorePrimTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/core/CorePrimTest.scala index 2976536218c..8550e41f03f 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/core/CorePrimTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/core/CorePrimTest.scala @@ -1,7 +1,9 @@ package org.enso.compiler.test.core import org.enso.compiler.test.CompilerTest +import org.enso.core.CoreGraph.DefinitionGen.Node.LocationVal import org.enso.core.CoreGraph.DefinitionGen.{ + AstStorage, Link, LiteralStorage, NameStorage, @@ -10,6 +12,7 @@ import org.enso.core.CoreGraph.DefinitionGen.{ } import org.scalatest.BeforeAndAfterEach import org.enso.graph.{Graph => PrimGraph} +import org.enso.syntax.text.AST /** This file tests the primitive, low-level operations on core. * @@ -22,7 +25,7 @@ import org.enso.graph.{Graph => PrimGraph} class CorePrimTest extends CompilerTest with BeforeAndAfterEach { // === Test Setup =========================================================== - import org.enso.core.CoreGraph.DefinitionGen.CoreGraph + import org.enso.core.CoreGraph.DefinitionGen._ import org.enso.core.CoreGraph.DefinitionGen.Link.Shape._ import org.enso.core.CoreGraph.DefinitionGen.Node.Shape._ import org.enso.core.CoreGraph.DefinitionGen.Node.Location._ @@ -33,12 +36,14 @@ class CorePrimTest extends CompilerTest with BeforeAndAfterEach { implicit var literalStorage: LiteralStorage = _ implicit var parentStorage: ParentStorage = _ implicit var nameStorage: NameStorage = _ + implicit var astStorage: AstStorage = _ override def beforeEach(): Unit = { graph = PrimGraph[CoreGraph]() literalStorage = LiteralStorage() parentStorage = ParentStorage() nameStorage = NameStorage() + astStorage = AstStorage() } // === Tests for Links ====================================================== @@ -124,6 +129,34 @@ class CorePrimTest extends CompilerTest with BeforeAndAfterEach { } } + node should "be able to be constructed without clobbering its fields" in { + val emptyLink = graph.addLink() + val nilLink = graph.addLink() + val consNode = Node.addRefined[MetaList] + + consNode.head = emptyLink + consNode.tail = nilLink + consNode.location = LocationVal[CoreGraph](20, 30) + consNode.parents = Vector() + + // Intentional re-assignment in reverse order to check for clobbering + consNode.tail = nilLink + consNode.head = emptyLink + + consNode.head shouldEqual emptyLink + consNode.tail shouldEqual nilLink + consNode.sourceStart shouldEqual 20 + consNode.sourceEnd shouldEqual 30 + consNode.parents shouldEqual Vector() + + consNode.wrapped match { + case MetaList.any(_) => succeed + case _ => fail + } + } + + // === Tests for Node Shapes ================================================ + nodeShape should "be able to be empty" in { val n1 = graph.addNode() @@ -807,13 +840,13 @@ class CorePrimTest extends CompilerTest with BeforeAndAfterEach { val n1 = graph.addNode() val l1 = graph.addLink() - Node.setShape[StructuralMatch](n1) + Node.setShape[StructuralPattern](n1) n1 match { - case StructuralMatch.any(n1) => + case StructuralPattern.any(n1) => n1.matchExpression = l1 - n1.structuralMatch shouldEqual StructuralMatchVal(l1) + n1.structuralPattern shouldEqual StructuralPatternVal(l1) case _ => fail } } @@ -822,13 +855,13 @@ class CorePrimTest extends CompilerTest with BeforeAndAfterEach { val n1 = graph.addNode() val l1 = graph.addLink() - Node.setShape[TypeMatch](n1) + Node.setShape[TypePattern](n1) n1 match { - case TypeMatch.any(n1) => + case TypePattern.any(n1) => n1.matchExpression = l1 - n1.typeMatch shouldEqual TypeMatchVal(l1) + n1.typePattern shouldEqual TypePatternVal(l1) case _ => fail } } @@ -837,13 +870,13 @@ class CorePrimTest extends CompilerTest with BeforeAndAfterEach { val n1 = graph.addNode() val l1 = graph.addLink() - Node.setShape[NamedMatch](n1) + Node.setShape[NamedPattern](n1) n1 match { - case NamedMatch.any(n1) => + case NamedPattern.any(n1) => n1.matchExpression = l1 - n1.namedMatch shouldEqual NamedMatchVal(l1) + n1.namedPattern shouldEqual NamedPatternVal(l1) case _ => fail } } @@ -851,11 +884,11 @@ class CorePrimTest extends CompilerTest with BeforeAndAfterEach { nodeShape should "be able to represent fallback patterns" in { val n1 = graph.addNode() - Node.setShape[FallbackMatch](n1) + Node.setShape[FallbackPattern](n1) n1 match { - case FallbackMatch.any(_) => succeed - case _ => fail + case FallbackPattern.any(_) => succeed + case _ => fail } } @@ -894,17 +927,31 @@ class CorePrimTest extends CompilerTest with BeforeAndAfterEach { } nodeShape should "be able to represent syntax errors" in { - val n1 = graph.addNode() - val l1 = graph.addLink() + val n1 = graph.addNode() + val ast = AST.Blank() Node.setShape[SyntaxError](n1) n1 match { case SyntaxError.any(n1) => - n1.errorNode = l1 + n1.errorAst = ast - n1.syntaxError shouldEqual SyntaxErrorVal(l1) + n1.syntaxError shouldEqual SyntaxErrorVal(ast) case _ => fail } } + + nodeShape should "be able to represent construction errors" in { + val n1 = graph.addNode() + val l1 = graph.addLink() + + Node.setShape[ConstructionError](n1) + + n1 match { + case ConstructionError.any(n1) => + n1.erroneousCore = l1 + + n1.constructionError shouldEqual ConstructionErrorVal(l1) + } + } } diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/core/CoreTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/core/CoreTest.scala index 5ebfb8747e1..3f87d7fc2da 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/core/CoreTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/core/CoreTest.scala @@ -10,6 +10,4 @@ import org.enso.compiler.test.CompilerTest // - Linked List // - Parent Link walk to same place // - etc. -class CoreTest extends CompilerTest { - -} +class CoreTest extends CompilerTest {} diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/core/SmartConstructorsTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/core/SmartConstructorsTest.scala new file mode 100644 index 00000000000..bf977ee97a6 --- /dev/null +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/core/SmartConstructorsTest.scala @@ -0,0 +1,1472 @@ +package org.enso.compiler.test.core + +import cats.data.NonEmptyList +import org.enso.compiler.core.Core +import org.enso.compiler.core.Core.Node.{Constants, Utility} +import org.enso.core.CoreGraph.DefinitionGen.Node.{Shape => NodeShape} +import org.enso.core.CoreGraph.{DefinitionGen => CoreDef} +import org.enso.graph.{Graph => PrimGraph} +import org.enso.syntax.text.{AST, Location => AstLocation} +import org.scalatest.{Assertion, BeforeAndAfterEach, Matchers, WordSpec} + +class SmartConstructorsTest + extends WordSpec + with Matchers + with BeforeAndAfterEach { + + // === Test Setup =========================================================== + + import Core._ + import CoreDef.Link.Shape._ + import CoreDef.Node.Location._ + import CoreDef.Node.ParentLinks._ + import CoreDef.Node.Shape._ + import PrimGraph.Component.Refined._ + import PrimGraph.VariantCast + + // === Useful Constants ===================================================== + + val constantLocationStart = 201 + val constantLocationEnd = 1337 + val dummyLocation: Core.Location = + CoreDef.Node.LocationVal(constantLocationStart, constantLocationEnd) + + // === Utilities ============================================================ + + /** Embodies the notion that a given node construction should fail with a + * provided result. + * + * It allows a literate style of assertion as is familiar from ScalaTest + * through use of an implicit class. + * + * @param maybeErr the result of the possibly-erroring node construction + * @param core an implicit instance of core + * @tparam T the type of the node construction when it succeeds + */ + implicit class ShouldFailWithResult[T <: CoreDef.Node.Shape]( + maybeErr: Core.ConsErrOr[T] + )(implicit core: Core) { + + /** Expresses that a node should fail to construct, and provide the + * erroneous core structures in [[errList]]. + * + * @param errList a meta list (on the core graph) containing the erroneous + * core + * @return a success if [[maybeErr]] is a failure and the contained error + * matches [[errList]], otherwise a failure + */ + def shouldFailWithResult[T <: CoreDef.Node.Shape]( + errList: RefinedNode[MetaList] + ): Assertion = { + maybeErr match { + case Left(err) => + err.erroneousCore.target match { + case NodeShape.MetaList.any(xs) => + if (Utility.listsAreEqual(xs, errList)) { + succeed + } else { + fail + } + case _ => fail + } + case Right(_) => fail + } + } + } + + // === Tests for Node Smart Constructors (Meta Shapes) ====================== + + "Empty nodes" should { + implicit val core: Core = new Core() + val emptyNode = Node.New.Empty() + + "have valid fields" in { + emptyNode.location shouldEqual Node.Constants.invalidLocation + emptyNode.parents shouldEqual Vector() + } + } + + "Meta list nodes" should { + implicit val core: Core = new Core() + val emptyNode = Node.New.Empty() + val nilNode = Node.New.MetaNil() + val listNode = Node.New.MetaList(emptyNode, nilNode).right.get + + "have valid fields" in { + listNode.location shouldEqual Node.Constants.invalidLocation + listNode.parents shouldEqual Vector() + } + + "have properly connected links" in { + listNode.head.source shouldEqual listNode + listNode.head.target shouldEqual emptyNode + listNode.tail.source shouldEqual listNode + listNode.tail.target shouldEqual nilNode + } + + "have children parented to the node" in { + emptyNode.parents should contain(listNode.head.ix) + nilNode.parents should contain(listNode.tail.ix) + } + + "fail to construct if tail isn't a valid meta list" in { + val node = Node.New.MetaList(emptyNode, emptyNode) + + node shouldFailWithResult Utility.coreListFrom(emptyNode) + } + } + + "Meta nil nodes" should { + implicit val core: Core = new Core() + val nilNode = Node.New.MetaNil() + + "have valid fields" in { + nilNode.location shouldEqual Node.Constants.invalidLocation + nilNode.parents shouldEqual Vector() + } + } + + "Meta true nodes" should { + implicit val core: Core = new Core() + val trueNode = Node.New.MetaTrue() + + "have valid fields" in { + trueNode.location shouldEqual Node.Constants.invalidLocation + trueNode.parents shouldEqual Vector() + } + } + + "Meta false nodes" should { + implicit val core: Core = new Core() + val falseNode = Node.New.MetaFalse() + + "have valid fields" in { + falseNode.location shouldEqual Node.Constants.invalidLocation + falseNode.parents shouldEqual Vector() + } + } + + // === Tests for Node Smart Constructors (Literals) ========================= + + "Numeric literal nodes" should { + implicit val core: Core = new Core() + val numLit = "1e-262" + val number = Node.New.NumericLiteral(numLit, dummyLocation) + + "have valid fields" in { + number.number shouldEqual numLit + number.location shouldEqual dummyLocation + number.parents shouldEqual Vector() + } + } + + "Text literal nodes" should { + implicit val core: Core = new Core() + val textLit = "FooBarBaz" + val text = Node.New.TextLiteral(textLit, dummyLocation) + + "have valid fields" in { + text.text shouldEqual textLit + text.location shouldEqual dummyLocation + text.parents shouldEqual Vector() + } + } + + "Foreign code literal nodes" should { + implicit val core: Core = new Core() + val codeLit = "lambda x: x + 1" + val code = Node.New.ForeignCodeLiteral(codeLit, dummyLocation) + + "have valid fields" in { + code.code shouldEqual codeLit + code.location shouldEqual dummyLocation + code.parents shouldEqual Vector() + } + } + + // === Tests for Node Smart Constructors (Names) ============================ + + "Name nodes" should { + implicit val core: Core = new Core() + val nameLit = "MyType" + val name = Node.New.Name(nameLit, dummyLocation) + + "have valid fields" in { + name.nameLiteral shouldEqual nameLit + name.location shouldEqual dummyLocation + name.parents shouldEqual Vector() + } + } + + "This name nodes" should { + implicit val core: Core = new Core() + val thisName = Node.New.ThisName(dummyLocation) + + "have valid fields" in { + thisName.location shouldEqual dummyLocation + thisName.parents shouldEqual Vector() + } + } + + "Here name nodes" should { + implicit val core: Core = new Core() + val hereName = Node.New.HereName(dummyLocation) + + "have valid fields" in { + hereName.location shouldEqual dummyLocation + hereName.parents shouldEqual Vector() + } + } + + // === Tests for Node Smart Constructors (Module) =========================== + + "Module nodes" should { + implicit val core: Core = new Core() + val importNil = Node.New.MetaNil() + val defNil = Node.New.MetaNil() + val name = Node.New.Name("MyModule", dummyLocation) + + val module = + Node.New.ModuleDef(name, importNil, defNil, dummyLocation).right.get + + "have valid fields" in { + module.location shouldEqual dummyLocation + module.parents shouldEqual Vector() + } + + "have properly connected links" in { + module.name.source shouldEqual module + module.name.target shouldEqual name + module.imports.source shouldEqual module + module.imports.target shouldEqual importNil + module.definitions.source shouldEqual module + module.definitions.target shouldEqual defNil + } + + "have children parented to the node" in { + name.parents should contain(module.name.ix) + importNil.parents should contain(module.imports.ix) + defNil.parents should contain(module.definitions.ix) + } + + "fail to construct if imports isn't a meta list" in { + val node = Node.New.ModuleDef(name, name, defNil, dummyLocation) + node shouldFailWithResult Utility.coreListFrom(name) + } + + "fail if construct definitions isn't a meta list" in { + val node = Node.New.ModuleDef(name, importNil, name, dummyLocation) + node shouldFailWithResult Utility.coreListFrom(name) + } + } + + "Import nodes" should { + implicit val core: Core = new Core() + val segmentsNil = Node.New.MetaNil() + val empty = Node.New.Empty() + val imp = + Node.New.Import(segmentsNil, dummyLocation).right.get + + "have valid fields" in { + imp.location shouldEqual dummyLocation + imp.parents shouldEqual Vector() + } + + "have properly connected links" in { + imp.segments.source shouldEqual imp + imp.segments.target shouldEqual segmentsNil + } + + "have children parented to the node" in { + segmentsNil.parents should contain(imp.segments.ix) + } + + "fail to construct if segments isn't a valid meta list" in { + val node = Node.New.Import(empty, dummyLocation) + + node shouldFailWithResult Utility.coreListFrom(empty) + } + } + + "Top-level binding nodes" should { + implicit val core: Core = new Core() + val emptyModule = Node.New.Empty() + val bindingSrc = Node.New.Empty() + val bindingTgt = Node.New.Empty() + val binding = + Node.New.Binding(bindingSrc, bindingTgt, dummyLocation) + val topLevelBinding = + Node.New.TopLevelBinding(emptyModule, binding, dummyLocation).right.get + + "have valid fields" in { + topLevelBinding.location shouldEqual dummyLocation + topLevelBinding.parents shouldEqual Vector() + } + + "have properly connected links" in { + topLevelBinding.module.source shouldEqual topLevelBinding + topLevelBinding.module.target shouldEqual emptyModule + topLevelBinding.binding.source shouldEqual topLevelBinding + topLevelBinding.binding.target shouldEqual binding + } + + "have children parented to the node" in { + emptyModule.parents should contain(topLevelBinding.module.ix) + binding.parents should contain(topLevelBinding.binding.ix) + } + + "fail to construct if binding isn't a valid binding" in { + val node = + Node.New.TopLevelBinding(emptyModule, bindingSrc, dummyLocation) + + node shouldFailWithResult Utility.coreListFrom(bindingSrc) + } + } + + // === Tests for Node Smart Constructors (Type Definitions) ================= + + "Atom definition nodes" should { + implicit val core: Core = new Core() + + val name = Node.New.Empty() + val argName = Node.New.Empty() + val args = Utility.coreListFrom(argName) + + val atomDef = Node.New.AtomDef(name, args, dummyLocation).right.get + + "have valid fields" in { + atomDef.location shouldEqual dummyLocation + atomDef.parents shouldEqual Vector() + } + + "have properly connected links" in { + atomDef.name.source shouldEqual atomDef + atomDef.name.target shouldEqual name + atomDef.args.source shouldEqual atomDef + atomDef.args.target shouldEqual args + } + + "have children parented to the node" in { + name.parents should contain(atomDef.name.ix) + args.parents should contain(atomDef.args.ix) + } + + "fail to construct if args is not a valid meta list" in { + val node = Node.New.AtomDef(name, argName, dummyLocation) + + node shouldFailWithResult Utility.coreListFrom(argName) + } + } + + "Type definition nodes" should { + implicit val core: Core = new Core() + + val name = Node.New.Empty() + val tParam = Node.New.Empty() + val tParams = Utility.coreListFrom(tParam) + + val bodyExpr = Node.New.Empty() + val body = Utility.coreListFrom(bodyExpr) + + val typeDef = Node.New.TypeDef(name, tParams, body, dummyLocation).right.get + + "have valid fields" in { + typeDef.location shouldEqual dummyLocation + typeDef.parents shouldEqual Vector() + } + + "have properly connected links" in { + typeDef.name.source shouldEqual typeDef + typeDef.name.target shouldEqual name + typeDef.typeParams.source shouldEqual typeDef + typeDef.typeParams.target shouldEqual tParams + typeDef.body.source shouldEqual typeDef + typeDef.body.target shouldEqual body + } + + "have children parented to the node" in { + name.parents should contain(typeDef.name.ix) + tParams.parents should contain(typeDef.typeParams.ix) + body.parents should contain(typeDef.body.ix) + } + + "fail to construct of the type params are not a valid meta list" in { + val node = Node.New.TypeDef(name, name, body, dummyLocation) + node shouldFailWithResult Utility.coreListFrom(name) + } + "fail to construct if the body is not a valid meta list" in { + val node = Node.New.TypeDef(name, tParams, name, dummyLocation) + node shouldFailWithResult Utility.coreListFrom(name) + } + } + + // === Tests for Node Smart Constructors (Typing) =========================== + + "Type ascription nodes" should { + implicit val core: Core = new Core() + + val typed = Node.New.Empty() + val sig = Node.New.Empty() + val ascription = Node.New.TypeAscription(typed, sig, dummyLocation) + + "have valid fields" in { + ascription.location shouldEqual dummyLocation + ascription.parents shouldEqual Vector() + } + + "have properly connected links" in { + ascription.typed.source shouldEqual ascription + ascription.typed.target shouldEqual typed + ascription.sig.source shouldEqual ascription + ascription.sig.target shouldEqual sig + } + + "have children parented to the node" in { + typed.parents should contain(ascription.typed.ix) + sig.parents should contain(ascription.sig.ix) + } + } + + "Context ascription nodes" should { + implicit val core: Core = new Core() + + val typed = Node.New.Empty() + val context = Node.New.Empty() + val ascription = Node.New.ContextAscription(typed, context, dummyLocation) + + "have valid fields" in { + ascription.location shouldEqual dummyLocation + ascription.parents shouldEqual Vector() + } + + "have properly connected links" in { + ascription.typed.source shouldEqual ascription + ascription.typed.target shouldEqual typed + ascription.context.source shouldEqual ascription + ascription.context.target shouldEqual context + } + + "have children parented to the node" in { + typed.parents should contain(ascription.typed.ix) + context.parents should contain(ascription.context.ix) + } + } + + "Typeset memeber nodes" should { + implicit val core: Core = new Core() + + val label = Node.New.Empty() + val memberType = Node.New.Empty() + val value = Node.New.Empty() + + val typesetMember = + Node.New.TypesetMember(label, memberType, value, dummyLocation) + + "have valid fields" in { + typesetMember.location shouldEqual dummyLocation + typesetMember.parents shouldEqual Vector() + } + + "have properly connected links" in { + typesetMember.label.source shouldEqual typesetMember + typesetMember.label.target shouldEqual label + typesetMember.memberType.source shouldEqual typesetMember + typesetMember.memberType.target shouldEqual memberType + typesetMember.value.source shouldEqual typesetMember + typesetMember.value.target shouldEqual value + } + + "have children parented to the node" in { + label.parents should contain(typesetMember.label.ix) + memberType.parents should contain(typesetMember.memberType.ix) + value.parents should contain(typesetMember.value.ix) + } + } + + "Typset subsumption nodes" should { + implicit val core: Core = new Core() + + val left = Node.New.Empty() + val right = Node.New.Empty() + + val op = Node.New.TypesetSubsumption(left, right, dummyLocation) + + "have valid fields" in { + op.location shouldEqual dummyLocation + op.parents shouldEqual Vector() + } + + "have properly connected links" in { + op.left.source shouldEqual op + op.left.target shouldEqual left + op.right.source shouldEqual op + op.right.target shouldEqual right + } + + "have children parented to the node" in { + left.parents should contain(op.left.ix) + right.parents should contain(op.right.ix) + } + } + + "Typset equality nodes" should { + implicit val core: Core = new Core() + + val left = Node.New.Empty() + val right = Node.New.Empty() + + val op = Node.New.TypesetEquality(left, right, dummyLocation) + + "have valid fields" in { + op.location shouldEqual dummyLocation + op.parents shouldEqual Vector() + } + + "have properly connected links" in { + op.left.source shouldEqual op + op.left.target shouldEqual left + op.right.source shouldEqual op + op.right.target shouldEqual right + } + + "have children parented to the node" in { + left.parents should contain(op.left.ix) + right.parents should contain(op.right.ix) + } + } + + "Typset concat nodes" should { + implicit val core: Core = new Core() + + val left = Node.New.Empty() + val right = Node.New.Empty() + + val op = Node.New.TypesetConcat(left, right, dummyLocation) + + "have valid fields" in { + op.location shouldEqual dummyLocation + op.parents shouldEqual Vector() + } + + "have properly connected links" in { + op.left.source shouldEqual op + op.left.target shouldEqual left + op.right.source shouldEqual op + op.right.target shouldEqual right + } + + "have children parented to the node" in { + left.parents should contain(op.left.ix) + right.parents should contain(op.right.ix) + } + } + + "Typset union nodes" should { + implicit val core: Core = new Core() + + val left = Node.New.Empty() + val right = Node.New.Empty() + + val op = Node.New.TypesetUnion(left, right, dummyLocation) + + "have valid fields" in { + op.location shouldEqual dummyLocation + op.parents shouldEqual Vector() + } + + "have properly connected links" in { + op.left.source shouldEqual op + op.left.target shouldEqual left + op.right.source shouldEqual op + op.right.target shouldEqual right + } + + "have children parented to the node" in { + left.parents should contain(op.left.ix) + right.parents should contain(op.right.ix) + } + } + + "Typset intersection nodes" should { + implicit val core: Core = new Core() + + val left = Node.New.Empty() + val right = Node.New.Empty() + + val op = Node.New.TypesetIntersection(left, right, dummyLocation) + + "have valid fields" in { + op.location shouldEqual dummyLocation + op.parents shouldEqual Vector() + } + + "have properly connected links" in { + op.left.source shouldEqual op + op.left.target shouldEqual left + op.right.source shouldEqual op + op.right.target shouldEqual right + } + + "have children parented to the node" in { + left.parents should contain(op.left.ix) + right.parents should contain(op.right.ix) + } + } + + "Typset subtraction nodes" should { + implicit val core: Core = new Core() + + val left = Node.New.Empty() + val right = Node.New.Empty() + + val op = Node.New.TypesetSubtraction(left, right, dummyLocation) + + "have valid fields" in { + op.location shouldEqual dummyLocation + op.parents shouldEqual Vector() + } + + "have properly connected links" in { + op.left.source shouldEqual op + op.left.target shouldEqual left + op.right.source shouldEqual op + op.right.target shouldEqual right + } + + "have children parented to the node" in { + left.parents should contain(op.left.ix) + right.parents should contain(op.right.ix) + } + } + + // === Tests for Node Smart Constructors (Function) ========================= + + "Lambda nodes" should { + implicit val core: Core = new Core() + + val arg = Node.New.Empty() + val body = Node.New.Empty() + + val lambda = Node.New.Lambda(arg, body, dummyLocation) + + "have valid fields" in { + lambda.location shouldEqual dummyLocation + lambda.parents shouldEqual Vector() + } + + "have properly connected links" in { + lambda.arg.source shouldEqual lambda + lambda.arg.target shouldEqual arg + lambda.body.source shouldEqual lambda + lambda.body.target shouldEqual body + } + + "have children parented to the node" in { + arg.parents should contain(lambda.arg.ix) + body.parents should contain(lambda.body.ix) + } + } + + "Function definition nodes" should { + implicit val core: Core = new Core() + + val name = Node.New.Empty() + val arg = Node.New.Empty() + val args = Utility.coreListFrom(arg) + val body = Node.New.Empty() + + val functionDef = + Node.New.FunctionDef(name, args, body, dummyLocation).right.get + + "have valid fields" in { + functionDef.location shouldEqual dummyLocation + functionDef.parents shouldEqual Vector() + } + + "have properly connected links" in { + functionDef.name.source shouldEqual functionDef + functionDef.name.target shouldEqual name + functionDef.args.source shouldEqual functionDef + functionDef.args.target shouldEqual args + functionDef.body.source shouldEqual functionDef + functionDef.body.target shouldEqual body + } + + "have children parented to the node" in { + name.parents should contain(functionDef.name.ix) + args.parents should contain(functionDef.args.ix) + body.parents should contain(functionDef.body.ix) + } + + "fail to construct if args is not a meta list" in { + val node = Node.New.FunctionDef(name, name, body, dummyLocation) + node shouldFailWithResult Utility.coreListFrom(name) + } + } + + "Method definition nodes" should { + implicit val core: Core = new Core() + + val targetPath = Node.New.Empty() + val name = Node.New.Empty() + val lamArg = Node.New.Empty() + val lamBody = Node.New.Empty() + val function = Node.New.Lambda(lamArg, lamBody, dummyLocation) + + val methodDef = + Node.New.MethodDef(targetPath, name, function, dummyLocation).right.get + + "have valid fields" in { + methodDef.location shouldEqual dummyLocation + methodDef.parents shouldEqual Vector() + } + + "have properly connected links" in { + methodDef.targetPath.source shouldEqual methodDef + methodDef.targetPath.target shouldEqual targetPath + methodDef.name.source shouldEqual methodDef + methodDef.name.target shouldEqual name + methodDef.function.source shouldEqual methodDef + methodDef.function.target shouldEqual function + } + + "have children parented to the node" in { + targetPath.parents should contain(methodDef.targetPath.ix) + name.parents should contain(methodDef.name.ix) + function.parents should contain(methodDef.function.ix) + } + + "fail to construct if function is not a valid function representation" in { + val node = Node.New.MethodDef(targetPath, name, name, dummyLocation) + node shouldFailWithResult Utility.coreListFrom(name) + } + } + + // === Tests for Node Smart Constructors (Def-Site Args) ==================== + + "Ignored argument nodes" should { + implicit val core: Core = new Core() + + val ignored = Node.New.IgnoredArgument(dummyLocation) + + "have valid fields" in { + ignored.location shouldEqual dummyLocation + ignored.parents shouldEqual Vector() + } + } + + "Definition argument nodes" should { + implicit val core: Core = new Core() + + val name = Node.New.Empty() + val suspended = Node.New.MetaTrue() + val default = Node.New.Empty() + + val arg = Node.New + .DefinitionArgument(name, suspended, default, dummyLocation) + .right + .get + + "have valid fields" in { + arg.location shouldEqual dummyLocation + arg.parents shouldEqual Vector() + } + + "have properly connected links" in { + arg.name.source shouldEqual arg + arg.name.target shouldEqual name + arg.suspended.source shouldEqual arg + arg.suspended.target shouldEqual suspended + arg.default.source shouldEqual arg + arg.default.target shouldEqual default + } + + "have children parented to the node" in { + name.parents should contain(arg.name.ix) + suspended.parents should contain(arg.suspended.ix) + default.parents should contain(arg.default.ix) + } + + "fail to construct if suspended is not a meta boolean" in { + val node = Node.New.DefinitionArgument(name, name, default, dummyLocation) + node shouldFailWithResult Utility.coreListFrom(name) + } + } + + // === Tests for Node Smart Constructors (Applications) ===================== + + "Application nodes" should { + implicit val core: Core = new Core() + + val function = Node.New.Empty() + val argument = Node.New.Empty() + + val application = Node.New.Application(function, argument, dummyLocation) + + "have valid fields" in { + application.location shouldEqual dummyLocation + application.parents shouldEqual Vector() + } + + "have properly connected links" in { + application.function.source shouldEqual application + application.function.target shouldEqual function + application.argument.source shouldEqual application + application.argument.target shouldEqual argument + } + + "have children parented to the node" in { + function.parents should contain(application.function.ix) + argument.parents should contain(application.argument.ix) + } + } + + "Infix application nodes" should { + implicit val core: Core = new Core() + + val left = Node.New.Empty() + val operator = Node.New.Empty() + val right = Node.New.Empty() + + val app = Node.New.InfixApplication(left, operator, right, dummyLocation) + + "have valid fields" in { + app.location shouldEqual dummyLocation + app.parents shouldEqual Vector() + } + + "have properly connected links" in { + app.left.source shouldEqual app + app.left.target shouldEqual left + app.operator.source shouldEqual app + app.operator.target shouldEqual operator + app.right.source shouldEqual app + app.right.target shouldEqual right + } + + "have children parented to the node" in { + left.parents should contain(app.left.ix) + operator.parents should contain(app.operator.ix) + right.parents should contain(app.right.ix) + } + } + + "Left section nodes" should { + implicit val core: Core = new Core() + + val arg = Node.New.Empty() + val operator = Node.New.Empty() + + val sec = Node.New.LeftSection(arg, operator, dummyLocation) + + "have valid fields" in { + sec.location shouldEqual dummyLocation + sec.parents shouldEqual Vector() + } + + "have properly connected links" in { + sec.arg.source shouldEqual sec + sec.arg.target shouldEqual arg + sec.operator.source shouldEqual sec + sec.operator.target shouldEqual operator + } + + "have children parented to the node" in { + arg.parents should contain(sec.arg.ix) + operator.parents should contain(sec.operator.ix) + } + } + + "Right section nodes" should { + implicit val core: Core = new Core() + + val operator = Node.New.Empty() + val arg = Node.New.Empty() + + val sec = Node.New.RightSection(operator, arg, dummyLocation) + + "have valid fields" in { + sec.location shouldEqual dummyLocation + sec.parents shouldEqual Vector() + } + + "have properly connected links" in { + sec.operator.source shouldEqual sec + sec.operator.target shouldEqual operator + sec.arg.source shouldEqual sec + sec.arg.target shouldEqual arg + } + + "have children parented to the node" in { + operator.parents should contain(sec.operator.ix) + arg.parents should contain(sec.arg.ix) + } + } + + "Centre section nodes" should { + implicit val core: Core = new Core() + + val operator = Node.New.Empty() + + val sec = Node.New.CentreSection(operator, dummyLocation) + + "have valid fields" in { + sec.location shouldEqual dummyLocation + sec.parents shouldEqual Vector() + } + + "have properly connected links" in { + sec.operator.source shouldEqual sec + sec.operator.target shouldEqual operator + } + + "have children parented to the node" in { + operator.parents should contain(sec.operator.ix) + } + } + + "Forced term nodes" should { + implicit val core: Core = new Core() + + val expression = Node.New.Empty() + + val forcedTerm = Node.New.ForcedTerm(expression, dummyLocation) + + "have valid fields" in { + forcedTerm.location shouldEqual dummyLocation + forcedTerm.parents shouldEqual Vector() + } + + "have properly connected links" in { + forcedTerm.expression.source shouldEqual forcedTerm + forcedTerm.expression.target shouldEqual expression + } + + "have children parented to the node" in { + expression.parents should contain(forcedTerm.expression.ix) + } + } + + // === Tests for Node Smart Constructors (Call-Site Args) =================== + + "Lambda shorthand argument nodes" should { + implicit val core: Core = new Core() + + val arg = Node.New.LambdaShorthandArgument(dummyLocation) + + "have valid fields" in { + arg.location shouldEqual dummyLocation + arg.parents shouldEqual Vector() + } + } + + "Call-site argument nodes" should { + implicit val core: Core = new Core() + + val expression = Node.New.Empty() + val name = Node.New.Empty() + + val arg = Node.New.CallSiteArgument(expression, name, dummyLocation) + + "have valid fields" in { + arg.location shouldEqual dummyLocation + arg.parents shouldEqual Vector() + } + + "have properly connected links" in { + arg.expression.source shouldEqual arg + arg.expression.target shouldEqual expression + arg.name.source shouldEqual arg + arg.name.target shouldEqual name + } + + "have children parented to the node" in { + expression.parents should contain(arg.expression.ix) + name.parents should contain(arg.name.ix) + } + } + + "Default suspension operator nodes" should { + implicit val core: Core = new Core() + + val suspend = Node.New.SuspendDefaultsOperator(dummyLocation) + + "have valid fields" in { + suspend.location shouldEqual dummyLocation + suspend.parents shouldEqual Vector() + } + } + + // === Tests for Node Smart Constructors (Structure) ======================== + + "Block nodes" should { + implicit val core: Core = new Core() + + val expressions = Utility.coreListFrom(Node.New.Empty()) + val returnVal = Node.New.Empty() + + val block = Node.New.Block(expressions, returnVal, dummyLocation).right.get + + "have valid fields" in { + block.location shouldEqual dummyLocation + block.parents shouldEqual Vector() + } + + "have properly connected links" in { + block.expressions.source shouldEqual block + block.expressions.target shouldEqual expressions + block.returnVal.source shouldEqual block + block.returnVal.target shouldEqual returnVal + } + + "have children parented to the node" in { + expressions.parents should contain(block.expressions.ix) + returnVal.parents should contain(block.returnVal.ix) + } + + "fail to construct if expressions is not a valid meta list" in { + val node = Node.New.Block(returnVal, returnVal, dummyLocation) + node shouldFailWithResult Utility.coreListFrom(returnVal) + } + } + + "Binding nodes" should { + implicit val core: Core = new Core() + + val name = Node.New.Empty() + val expression = Node.New.Empty() + + val binding = Node.New.Binding(name, expression, dummyLocation) + + "have valid fields" in { + binding.location shouldEqual dummyLocation + binding.parents shouldEqual Vector() + } + + "have properly connected links" in { + binding.name.source shouldEqual binding + binding.name.target shouldEqual name + binding.expression.source shouldEqual binding + binding.expression.target shouldEqual expression + } + + "have children parented to the node" in { + name.parents should contain(binding.name.ix) + expression.parents should contain(binding.expression.ix) + } + } + + // === Tests for Node Smart Constructors (Case Expression) ================== + + "Case expression nodes" should { + implicit val core: Core = new Core() + + val scrutinee = Node.New.Empty() + val branches = Utility.coreListFrom(Node.New.Empty()) + + val caseExpr = + Node.New.CaseExpr(scrutinee, branches, dummyLocation).right.get + + "have valid fields" in { + caseExpr.location shouldEqual dummyLocation + caseExpr.parents shouldEqual Vector() + } + + "have properly connected links" in { + caseExpr.scrutinee.source shouldEqual caseExpr + caseExpr.scrutinee.target shouldEqual scrutinee + caseExpr.branches.source shouldEqual caseExpr + caseExpr.branches.target shouldEqual branches + } + + "have children parented to the node" in { + scrutinee.parents should contain(caseExpr.scrutinee.ix) + branches.parents should contain(caseExpr.branches.ix) + } + + "fail to construct if branches is not a valid meta list" in { + val node = Node.New.CaseExpr(scrutinee, scrutinee, dummyLocation) + node shouldFailWithResult Utility.coreListFrom(scrutinee) + } + } + + "Case branch nodes" should { + implicit val core: Core = new Core() + + val pattern = Node.New.Empty() + val expression = Node.New.Empty() + + val caseBranch = Node.New.CaseBranch(pattern, expression, dummyLocation) + + "have valid fields" in { + caseBranch.location shouldEqual dummyLocation + caseBranch.parents shouldEqual Vector() + } + + "have properly connected links" in { + caseBranch.pattern.source shouldEqual caseBranch + caseBranch.pattern.target shouldEqual pattern + caseBranch.expression.source shouldEqual caseBranch + caseBranch.expression.target shouldEqual expression + } + + "have children parented to the node" in { + pattern.parents should contain(caseBranch.pattern.ix) + expression.parents should contain(caseBranch.expression.ix) + } + } + + "Structural pattern nodes" should { + implicit val core: Core = new Core() + + val matchExpression = Node.New.Empty() + + val pattern = Node.New.StructuralPattern(matchExpression, dummyLocation) + + "have valid fields" in { + pattern.location shouldEqual dummyLocation + pattern.parents shouldEqual Vector() + } + + "have properly connected links" in { + pattern.matchExpression.source shouldEqual pattern + pattern.matchExpression.target shouldEqual matchExpression + } + + "have children parented to the node" in { + matchExpression.parents should contain(pattern.matchExpression.ix) + } + } + + "Type pattern nodes" should { + implicit val core: Core = new Core() + + val matchExpression = Node.New.Empty() + + val pattern = Node.New.TypePattern(matchExpression, dummyLocation) + + "have valid fields" in { + pattern.location shouldEqual dummyLocation + pattern.parents shouldEqual Vector() + } + + "have properly connected links" in { + pattern.matchExpression.source shouldEqual pattern + pattern.matchExpression.target shouldEqual matchExpression + } + + "have children parented to the node" in { + matchExpression.parents should contain(pattern.matchExpression.ix) + } + } + + "Named pattern nodes" should { + implicit val core: Core = new Core() + + val matchExpression = Node.New.Empty() + + val pattern = Node.New.NamedPattern(matchExpression, dummyLocation) + + "have valid fields" in { + pattern.location shouldEqual dummyLocation + pattern.parents shouldEqual Vector() + } + + "have properly connected links" in { + pattern.matchExpression.source shouldEqual pattern + pattern.matchExpression.target shouldEqual matchExpression + } + + "have children parented to the node" in { + matchExpression.parents should contain(pattern.matchExpression.ix) + } + } + + "Fallback pattern nodes" should { + implicit val core: Core = new Core() + + val pattern = Node.New.FallbackPattern(dummyLocation) + + "have valid fields" in { + pattern.location shouldEqual dummyLocation + pattern.parents shouldEqual Vector() + } + } + + // === Tests for Node Smart Constructors (Comments) ========================= + + "Doc comment nodes" should { + implicit val core: Core = new Core() + + val commented = Node.New.Empty() + val doc = Node.New.Empty() + + val docComment = Node.New.DocComment(commented, doc, dummyLocation) + + "have valid fields" in { + docComment.location shouldEqual dummyLocation + docComment.parents shouldEqual Vector() + } + + "have properly connected links" in { + docComment.commented.source shouldEqual docComment + docComment.commented.target shouldEqual commented + docComment.doc.source shouldEqual docComment + docComment.doc.target shouldEqual doc + } + + "have children parented to the node" in { + commented.parents should contain(docComment.commented.ix) + doc.parents should contain(docComment.doc.ix) + } + } + + // === Tests for Node Smart Constructors (Foreign) ========================== + + "Foreign definition nodes" should { + implicit val core: Core = new Core() + + val language = Node.New.Empty() + val code = + Node.New.ForeignCodeLiteral("lambda x: x + 1", dummyLocation) + + val foreignDefinition = + Node.New.ForeignDefinition(language, code, dummyLocation).right.get + + "have valid fields" in { + foreignDefinition.location shouldEqual dummyLocation + foreignDefinition.parents shouldEqual Vector() + } + + "have properly connected links" in { + foreignDefinition.language.source shouldEqual foreignDefinition + foreignDefinition.language.target shouldEqual language + foreignDefinition.code.source shouldEqual foreignDefinition + foreignDefinition.code.target shouldEqual code + } + + "have children parented to the node" in { + language.parents should contain(foreignDefinition.language.ix) + code.parents should contain(foreignDefinition.code.ix) + } + + "fail to construct of code is not a valid foreign code literal" in { + val node = Node.New.ForeignDefinition(language, language, dummyLocation) + node shouldFailWithResult Utility.coreListFrom(language) + } + } + + // === Tests for Node Smart Constructors (Errors) =========================== + + "Syntax error nodes" should { + implicit val core: Core = new Core() + + val astWithLocation: AST = AST + .Blank() + .setLocation( + AstLocation(dummyLocation.sourceStart, dummyLocation.sourceEnd) + ) + val astNoLocation: AST = AST.Blank() + + val syntaxError = Node.New.SyntaxError(astWithLocation) + + "have valid fields" in { + syntaxError.parents shouldEqual Vector() + } + + "inherit the location from the AST" in { + syntaxError.location shouldEqual dummyLocation + } + + "default to an invalid location if the AST does not provide any" in { + val errorNoLocation = Node.New.SyntaxError(astNoLocation) + errorNoLocation.location shouldEqual Constants.invalidLocation + } + } + + "Construction error nodes" should { + implicit val core: Core = new Core() + + val errNode = Node.New.Empty() + val errList = Utility.coreListFrom(errNode) + val error = Node.New.ConstructionError(errList, dummyLocation) + + "have valid fields" in { + error.location shouldEqual dummyLocation + error.parents shouldEqual Vector() + } + + "have properly connected links" in { + error.erroneousCore.source shouldEqual error + error.erroneousCore.target shouldEqual errList + } + + "have children parented to the node" in { + errList.parents should contain(error.erroneousCore.ix) + } + + "use a passed-in meta list unchanged" in { + error.erroneousCore.target shouldEqual errList + } + + "contain a meta-list if passed a single node" in { + val input = errNode + val err = Node.New.ConstructionError(input, dummyLocation) + + input.wrapped.is[NodeShape.Empty] shouldEqual true + + Utility.isListNode(input) should not equal true + Utility.isListNode(err.erroneousCore.target) shouldEqual true + + // The only element in that list should be the single node + err.erroneousCore.target.unsafeAs[MetaList].head.target shouldEqual input + err.erroneousCore.target + .unsafeAs[MetaList] + .tail + .target + .is[MetaNil] shouldBe true + } + } + + // === Tests for Node Utility Functions ===================================== + + "Lists" should { + implicit val core: Core = new Core() + + val empty1 = Node.New.Empty().wrapped + val empty2 = Node.New.Empty().wrapped + + val list1 = Utility.coreListFrom(NonEmptyList(empty1, List(empty2))) + val list2 = Utility.coreListFrom(NonEmptyList(empty1, List(empty2))) + val list3 = Utility.coreListFrom(NonEmptyList(empty2, List(empty1))) + + "be defined as equal when the child nodes are equal" in { + Utility.listsAreEqual(list1, list2) shouldEqual true + } + + "be defined as not equal when the child nodes are not equal" in { + Utility.listsAreEqual(list1, list3) shouldEqual false + } + } + + "Nodes for meta booleans" should { + implicit val core: Core = new Core() + + val emptyNode = Node.New.Empty() + val trueNode = Node.New.MetaTrue() + val falseNode = Node.New.MetaFalse() + + "be correctly identified" in { + Utility.isBoolNode(emptyNode) shouldEqual false + Utility.isBoolNode(trueNode) shouldEqual true + Utility.isBoolNode(falseNode) shouldEqual true + } + } + + "Nodes for meta lists" should { + implicit val core: Core = new Core() + val emptyNode = Node.New.Empty() + val nilNode = Node.New.MetaNil() + val consNode = Node.New.MetaList(emptyNode, nilNode).right.get + + "be correctly identified" in { + Utility.isListNode(emptyNode) shouldEqual false + Utility.isListNode(nilNode) shouldEqual true + Utility.isListNode(consNode) shouldEqual true + } + } + + "Core lists" should { + implicit val core: Core = new Core() + + val emptyNode1 = Node.New.Empty().wrapped + val emptyNode2 = Node.New.Empty().wrapped + val emptyNode3 = Node.New.Empty().wrapped + + val listOfOne = Utility.coreListFrom(emptyNode1) + val listOfMany = Utility + .coreListFrom(NonEmptyList(emptyNode1, List(emptyNode2, emptyNode3))) + + "be able to be constructed from arbitrary nodes" in { + listOfOne.head.target shouldEqual emptyNode1 + + listOfOne.tail.target match { + case NodeShape.MetaNil.any(_) => + case _ => fail + } + + listOfMany.head.target shouldEqual emptyNode1 + + listOfMany.tail.target match { + case NodeShape.MetaList.any(e2) => + e2.head.target shouldEqual emptyNode2 + + e2.tail.target match { + case NodeShape.MetaList.any(e3) => + e3.head.target shouldEqual emptyNode3 + e3.tail.target match { + case NodeShape.MetaNil.any(_) => succeed + case _ => fail + } + case _ => fail + } + case _ => fail + } + } + } + + // === Tests for Node Conversions =========================================== + + "AST locations" should { + val startLoc = 1232 + val endLoc = 1337 + + val astLoc = AstLocation(startLoc, endLoc) + val coreLoc = CoreDef.Node.LocationVal(startLoc, endLoc) + + "be converted losslessly to core locations" in { + Node.Conversions.astLocationToNodeLocation(astLoc) shouldEqual coreLoc + } + } + + // === Tests for Link Smart Constructors ==================================== + + "Connected links" should { + implicit val core: Core = new Core() + + val n1 = Node.New.Empty() + val n2 = Node.New.Empty() + + val link = Link.New.Connected(n1, n2) + + "be able to be made with a source and target" in { + link.source shouldEqual n1 + link.target shouldEqual n2 + } + + "be a parent of their target" in { + n2.parents should contain(link.ix) + } + } + + "Disconnected links" should { + implicit val core: Core = new Core() + + val sourceNode = Node.New.MetaNil() + + val link = Link.New.Disconnected(sourceNode) + + "be able to be made with only a source" in { + link.source shouldEqual sourceNode + + link.target match { + case NodeShape.Empty.any(_) => succeed + case _ => fail + } + } + + "be a parent of their empty target" in { + link.target.parents should contain(link.ix) + } + } +}