diff --git a/.gitignore b/.gitignore index b5ab6e224fb..09bfbd5e267 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,4 @@ bench-report.xml ######### .editorconfig +.bloop diff --git a/.scalafmt.conf b/.scalafmt.conf index c7887711b53..7196882dbc0 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -63,10 +63,10 @@ verticalMultiline.excludeDanglingParens = [ // Rewrite Rules rewrite.rules = [ - ExpandImportSelectors, PreferCurlyFors, RedundantParens, SortModifiers, + SortImports, ] rewrite.sortModifiers.order = [ "implicit", "final", "sealed", "abstract", diff --git a/build.sbt b/build.sbt index 87725c280ee..de5110f2105 100644 --- a/build.sbt +++ b/build.sbt @@ -35,6 +35,7 @@ scalacOptions in ThisBuild ++= Seq( "-language:higherKinds", // Allow higher-kinded types "-language:implicitConversions", // Allow definition of implicit functions called views "-unchecked", // Enable additional warnings where generated code depends on assumptions. + "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access. "-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver. "-Xlint:by-name-right-associative", // By-name parameter of right associative operator. "-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error. @@ -50,9 +51,10 @@ scalacOptions in ThisBuild ++= Seq( "-Xlint:poly-implicit-overload", // Parameterized overloaded implicit methods are not visible as view bounds. "-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field. "-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component. - "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope. "-Xlint:unsound-match", // Pattern match may not be typesafe. + "-Xmacro-settings:-logging@org.enso", // Disable the debug logging globally. "-Yno-adapted-args", // Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver. + "-Ypartial-unification", // Enable partial unification (which is enabled by default in Scala 2.13). "-Ypartial-unification", // Enable partial unification in type constructor inference "-Ywarn-dead-code", // Warn when dead code is identified. "-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined. @@ -61,18 +63,18 @@ scalacOptions in ThisBuild ++= Seq( "-Ywarn-nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'. "-Ywarn-nullary-unit", // Warn when nullary methods return Unit. "-Ywarn-numeric-widen", // Warn when numerics are widened. - "-Ywarn-unused:implicits", // Warn if an implicit parameter is unused. "-Ywarn-unused:imports", // Warn if an import selector is not referenced. "-Ywarn-unused:locals", // Warn if a local definition is unused. - "-Ywarn-unused:params", // Warn if a value parameter is unused. "-Ywarn-unused:patvars", // Warn if a variable bound in a pattern is unused. "-Ywarn-unused:privates", // Warn if a private member is unused. - "-Ywarn-value-discard", // Warn when non-Unit expression results are unused. - "-Ypartial-unification", // Enable partial unification (which is enabled by default in Scala 2.13). - "-Xmacro-settings:-logging@org.enso", // Disable the debug logging globally. - "-Xcheckinit" // Wrap field accessors to throw an exception on uninitialized access. + "-Ywarn-value-discard" // Warn when non-Unit expression results are unused. ) +// ENABLE THIS IN Scala 2.13.1 (where import annotation.unused is available). +// "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope. +// "-Ywarn-unused:implicits", // Warn if an implicit parameter is unused. +// "-Ywarn-unused:params", // Warn if a value parameter is unused. + ///////////////////////////////// //// Benchmark Configuration //// ///////////////////////////////// @@ -90,14 +92,15 @@ lazy val buildNativeImage = lazy val enso = (project in file(".")) .settings(version := "0.1") .aggregate( - syntax, - pkg, - runtime, - flexer, - unused, - syntax_definition, file_manager, - project_manager + flexer, + graph, + pkg, + project_manager, + runtime, + syntax, + syntax_definition, + unused ) .settings(Global / concurrentRestrictions += Tags.exclusive(Exclusive)) @@ -115,8 +118,11 @@ val monocle = { } val cats = { Seq( - "org.typelevel" %% "cats-core" % "2.0.0-RC1", - "org.typelevel" %% "kittens" % "2.0.0" + "org.typelevel" %% "cats-core" % "2.0.0", + "org.typelevel" %% "cats-effect" % "2.0.0", + "org.typelevel" %% "cats-free" % "2.0.0", + "org.typelevel" %% "cats-macros" % "2.0.0", + "org.typelevel" %% "kittens" % "2.0.0" ) } @@ -149,9 +155,11 @@ val jmh = Seq( "org.openjdk.jmh" % "jmh-generator-annprocess" % "1.21" % Benchmark ) -////////////////////// -//// Sub-Projects //// -////////////////////// +val silencerVersion = "1.4.4" + +//////////////////////////// +//// Internal Libraries //// +//////////////////////////// lazy val logger = (project in file("common/scala/logger")) .dependsOn(unused) @@ -182,6 +190,34 @@ lazy val syntax_definition = (project in file("common/scala/syntax/definition")) ) ) +lazy val graph = (project in file("common/scala/graph/")) + .dependsOn(logger) + .configs(Test) + .settings( + version := "0.1", + scalacOptions -= "-deprecation", // FIXME + resolvers ++= Seq( + Resolver.sonatypeRepo("releases"), + Resolver.sonatypeRepo("snapshots") + ), + libraryDependencies ++= scala_compiler ++ Seq( + "com.chuusai" %% "shapeless" % "2.3.3", + "io.estatico" %% "newtype" % "0.4.3", + "org.scalatest" %% "scalatest" % "3.2.0-SNAP10" % Test, + "org.scalacheck" %% "scalacheck" % "1.14.0" % Test, + "com.github.julien-truffaut" %% "monocle-core" % "2.0.0" + ), + libraryDependencies ++= Seq( + compilerPlugin( + "com.github.ghik" % "silencer-plugin" % silencerVersion cross CrossVersion.full + ), + "com.github.ghik" % "silencer-lib" % silencerVersion % Provided cross CrossVersion.full + ), + addCompilerPlugin( + "org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full + ) + ) + lazy val syntax = (project in file("common/scala/syntax/specialization")) .dependsOn(logger, flexer, syntax_definition) .configs(Test) @@ -220,6 +256,36 @@ lazy val pkg = (project in file("common/scala/pkg")) libraryDependencies ++= circe ++ Seq("commons-io" % "commons-io" % "2.6") ) +lazy val file_manager = (project in file("common/scala/file-manager")) + .settings( + (Compile / mainClass) := Some("org.enso.filemanager.FileManager") + ) + .settings( + libraryDependencies ++= akka, + libraryDependencies += akkaSLF4J, + libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3", + libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.0-SNAP10" % Test, + libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" % Test, + libraryDependencies += akkaTestkitTyped, + libraryDependencies += "commons-io" % "commons-io" % "2.6", + libraryDependencies += "io.methvin" % "directory-watcher" % "0.9.6" + ) + +lazy val project_manager = (project in file("common/scala/project-manager")) + .settings( + (Compile / mainClass) := Some("org.enso.projectmanager.Server") + ) + .settings( + libraryDependencies ++= akka, + libraryDependencies ++= circe, + libraryDependencies += "io.spray" %% "spray-json" % "1.3.5" + ) + .dependsOn(pkg) + +////////////////////// +//// Sub Projects //// +////////////////////// + val truffleRunOptions = Seq( "-Dgraal.TruffleIterativePartialEscape=true", "-XX:-UseJVMCIClassLoader", @@ -316,29 +382,3 @@ lazy val runtime = (project in file("engine/runtime")) ) .dependsOn(pkg) .dependsOn(syntax) - -lazy val file_manager = (project in file("common/scala/file-manager")) - .settings( - (Compile / mainClass) := Some("org.enso.filemanager.FileManager") - ) - .settings( - libraryDependencies ++= akka, - libraryDependencies += akkaSLF4J, - libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3", - libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.0-SNAP10" % Test, - libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" % Test, - libraryDependencies += akkaTestkitTyped, - libraryDependencies += "commons-io" % "commons-io" % "2.6", - libraryDependencies += "io.methvin" % "directory-watcher" % "0.9.6" - ) - -lazy val project_manager = (project in file("common/scala/project-manager")) - .settings( - (Compile / mainClass) := Some("org.enso.projectmanager.Server") - ) - .settings( - libraryDependencies ++= akka, - libraryDependencies ++= circe, - libraryDependencies += "io.spray" %% "spray-json" % "1.3.5" - ) - .dependsOn(pkg) diff --git a/common/scala/graph/src/main/scala/org/enso/graph/Graph.scala b/common/scala/graph/src/main/scala/org/enso/graph/Graph.scala new file mode 100644 index 00000000000..d08bfeb2ede --- /dev/null +++ b/common/scala/graph/src/main/scala/org/enso/graph/Graph.scala @@ -0,0 +1,590 @@ +package org.enso.graph + +import shapeless.ops.{hlist, nat} +import shapeless.{::, HList, HNil, Nat} + +// Don't use AnyType here, as it gets boxed sometimes. +import io.estatico.newtype.macros.newtype + +// Type-level literals (_0, _1, ...) +import shapeless.nat._ + +/* TODO [AA, WD] The following are features that we want for this graph: + * - Graphviz output for visualisations. + * - Utilities for copying (sub-)graphs. + * - Storage should keep a free-list and re-use space in the underlying buffers + * 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`. + */ + +/** This file contains the implementation of an incredibly generic graph. + * + * There are a few key notes that should be kept in mind when analysing this + * solution: + * - This is a _highly_ generic graph structure, which provides the plumbing + * for building your own graph structures. + * - It only provides an _unsafe_ API. As a result, this graph should never be + * used directly by client code. Instead, it should be used to implement a + * custom graph structure that provides a safe API. + */ +// ============================================================================ +// === HList generic utilities ================================================ +// ============================================================================ + +// ================ +// === HListSum === +// ================ + +/** Sums a [[HList]] that contains only [[Nat]]. + * + * @tparam L the [[HList]] to sum + */ +trait HListSum[L <: HList] { + type Out <: Nat +} +object HListSum { + type Aux[L <: HList, X] = HListSum[L] { type Out = X } + + def apply[L <: HList](implicit ev: HListSum[L]): Aux[L, ev.Out] = ev + + implicit val onNil: HListSum.Aux[HNil, _0] = + new HListSum[HNil] { type Out = _0 } + + implicit def onCons[H <: Nat, T <: HList, TS <: Nat]( + implicit + rest: HListSum.Aux[T, TS], + all: nat.Sum[H, TS] + ): HListSum.Aux[H :: T, all.Out] = + new HListSum[H :: T] { type Out = all.Out } +} + +// ======================= +// === HListOfNatToVec === +// ======================= + +/** Converts an [[HList]] of [[Nat]] to a vactor containing those same numbers + * as integers. + * + * @tparam L the [[HList]] to convert + */ +trait HListOfNatToVec[L <: HList] { + val out: Vector[Int] +} +object HListOfNatToVec { + implicit def onNil: HListOfNatToVec[HNil] = + new HListOfNatToVec[HNil] { val out = Vector[Int]() } + + implicit def onCons[Head <: Nat, Tail <: HList]( + implicit + tail: HListOfNatToVec[Tail], + head: nat.ToInt[Head] + ): HListOfNatToVec[Head :: Tail] = new HListOfNatToVec[Head :: Tail] { + val out = head() +: tail.out + } +} + +// ====================== +// === HListTakeUntil === +// ====================== + +/** Takes members from an [[HList]] until it reaches a specified member [[T]]. + * + * The sentinel member [[T]] is not included in the result list. For example, + * the following code will result in the list `A :: HNil`: + * + * {{{ + * type MyList = A :: B :: C :: HNil + * type Sentinel = B + * + * type HListTakeUntil[Sentinel, MyList] + * }}} + * + * If the sentinel [[T]] is not found in [[List]], the entire list is returned. + * + * @tparam T the sentinel member + * @tparam List the list to take members from + */ +trait HListTakeUntil[T, List <: HList] { + type Out <: HList +} +object HListTakeUntil extends HListTakeUntilDefaults { + type Aux[T, List <: HList, X] = HListTakeUntil[T, List] { type Out = X } + + def apply[T, List <: HList]( + implicit ev: HListTakeUntil[T, List] + ): Aux[T, List, ev.Out] = ev + + implicit def onNil[T]: HListTakeUntil.Aux[T, HNil, HNil] = + new HListTakeUntil[T, HNil] { type Out = HNil } + + implicit def onConsFound[Head, Tail <: HList] + : HListTakeUntil.Aux[Head, Head :: Tail, HNil] = + new HListTakeUntil[Head, Head :: Tail] { type Out = HNil } +} + +trait HListTakeUntilDefaults { + implicit def onConsNotFound[T, Head, Tail <: HList, Tail2 <: HList]( + implicit + ev1: HListTakeUntil.Aux[T, Tail, Tail2] + ): HListTakeUntil.Aux[T, Head :: Tail, Head :: Tail2] = + new HListTakeUntil[T, Head :: Tail] { type Out = Head :: Tail2 } +} + +// ============================================================================ +// === Graph-specific utilities =============================================== +// ============================================================================ + +// ============= +// === Sized === +// ============= + +/** An abstraction for sized objects. + * + * Every sized object is aware of the size it occupies (specified as a number + * of [[Int]]) at compile time. + * + * @tparam T the [[Sized]] type + */ +trait Sized[T] { + type Out <: Nat +} +object Sized { + type Aux[T, X] = Sized[T] { type Out = X } + def apply[T](implicit ev: Sized[T]): Aux[T, ev.Out] = ev + + implicit def instance[ + List <: HList, + ListOfSizes <: HList, + TotalSize <: Nat + ]( + implicit + ev1: MapSized.Aux[List, ListOfSizes], + ev2: HListSum.Aux[ListOfSizes, TotalSize] + ): Sized.Aux[List, TotalSize] = + new Sized[List] { type Out = TotalSize } +} + +/** A utility for accessing a the Size of a [[Sized]] object as an [[Int]]. */ +trait KnownSize[T] extends Sized[T] { + val asInt: Int +} +object KnownSize { + implicit def instance[T, Size <: Nat]( + implicit + ev: Sized.Aux[T, Size], + sizeEv: nat.ToInt[Size] + ): KnownSize[T] = new KnownSize[T] { val asInt: Int = sizeEv() } +} + +// ================ +// === MapSized === +// ================ + +/** Extracts the sizes of all types in a list of types. All of these types must + * be `Sized`. + * + * @tparam L the list of elements to extract sizes from + */ +trait MapSized[L <: HList] { + type Out <: HList +} +object MapSized { + type Aux[L <: HList, X] = MapSized[L] { type Out = X } + + def apply[L <: HList](implicit ev: MapSized[L]): Aux[L, ev.Out] = ev + + implicit val onNil: MapSized.Aux[HNil, HNil] = + new MapSized[HNil] { type Out = HNil } + + implicit def onCons[H, T <: HList, TS <: HList, HSize <: Nat]( + implicit + rest: MapSized.Aux[T, TS], + headSize: Sized.Aux[H, HSize] + ): MapSized.Aux[H :: T, HSize :: TS] = + new MapSized[H :: T] { type Out = HSize :: TS } +} + +// ================= +// === SizeUntil === +// ================= + +/** Computes the size of the types in a HList up to some sentinel [[Elem]]. + * + * When summing the sizes in the list, the sentinel element [[Elem]] is not + * included in the sum. + * + * @tparam Elem the type of the element to stop computing at + * @tparam List the list of types to compute over + */ +trait SizeUntil[Elem, List <: HList] { + type Out <: Nat + val asInt: Int +} +object SizeUntil { + type Aux[Elem, List <: HList, X] = SizeUntil[Elem, List] { type Out = X } + + def apply[Elem, List <: HList]( + implicit ev: SizeUntil[Elem, List] + ): Aux[Elem, List, ev.Out] = ev + + implicit def instance[ + Elem, + List <: HList, + PriorElems <: HList, + PriorFieldSizes <: HList, + PriorFieldsSize <: Nat + ]( + implicit + ev1: HListTakeUntil.Aux[Elem, List, PriorElems], + ev2: MapSized.Aux[PriorElems, PriorFieldSizes], + ev3: HListSum.Aux[PriorFieldSizes, PriorFieldsSize], + sizeAsInt: nat.ToInt[PriorFieldsSize] + ): SizeUntil.Aux[Elem, List, PriorFieldsSize] = + new SizeUntil[Elem, List] { + type Out = PriorFieldsSize + val asInt = sizeAsInt() + } +} + +// ============================================================================ +// === Graph ================================================================== +// ============================================================================ + +/** A generic graph implementation. + * + * The type [[Graph]] should not be used directly by programs that use this + * library. Instead, it should be used to implement custom graph instances by + * extending the [[Graph]] trait. + */ +trait Graph +object Graph { + + // ========================== + // === Smart Constructors === + // ========================== + + def apply[G <: Graph: GraphInfo](): GraphData[G] = new GraphData[G]() + + // ================= + // === Component === + // ================= + + /** A graph component is a type of thing that can be stored in the graph. + * + * Components can be arbitrary, such as nodes, edges, groups, and so on. They + * are defined entirely by the users, and this is done by extending the + * [[Component]] trait. Please see the tests for examples of how this can be + * done. + */ + trait Component + object Component { + + // === Ref === + + /** A generic reference to a graph component. + * + * For example, the `Node` type could be defined as follows, where `Nodes` + * is the appropriate component in the graph. + * + * {{{ + * type Node = Ref[MyGraph, Nodes] + * }}} + */ + @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]] + * to encode properties into the graph structure. This information can be + * type information, but can also be used to tag graph components with + * arbitrary properties. + * + * For example, a node with information that its shape is `App` can be + * encoded having the following type `Refined[Shape, App, Node]`. + */ + @newtype + final case class Refined[C <: Component.Field, Spec, T](wrapped: T) + object Refined { + implicit def unwrap[C <: Component.Field, S, T]( + t: Refined[C, S, T] + ): T = { t.wrapped } + } + + // === List === + + /** Defines the set of components in a graph by assigning a type to the + * `Out` parameter when implementing the trait. + * + * This is used to encode the graph structure at the type level. An example + * is as follows: + * + * {{{ + * implicit def components = new Component.List[MyGraph] { + * type Out = Nodes :: Edges :: HNil + * } + * }}} + * + * @tparam G the graph for which the components are defined. + */ + trait List[G <: Graph] { + type Out <: HList + } + object List { + type Aux[G <: Graph, X] = List[G] { type Out = X } + } + + // === Field === + + /** A field is a portion of a [[Component]]. + * + * They are used to attribute data to components, and store data within a + * component. + * + * An example would be a component `Node`, that consists of fields such as + * `ParentLink` and `Shape`. + */ + trait Field + object Field { + + /** Defines the set of fields for a given kind of component. + * + * An example is as follows: + * + * {{{ + * implicit def nodeFields = new Component.Field.List[MyGraph, Nodes] { + * type Out = Node.Shape :: Node.ParentLink :: HNil + * } + * }}} + * + * @tparam G the graph to which the component type [[C]] belongs + * @tparam C the component type to which the fields in the list belong + */ + trait List[G <: Graph, C <: Component] { type Out <: HList } + object List { + type Aux[G <: Graph, C <: Component, X] = List[G, C] { type Out = X } + } + } + + // === Storage === + + /** Specialized storage for component data. + * + * We intentionally do not use [[scala.collection.mutable.ArrayBuffer]] as + * it cannot be specialised for primitive types. [[Array]], on the other + * hand, can be. + */ + final class Storage(elemSize: Int) { + var length: Int = 0 + var array: Array[Int] = new Array[Int](length) + + // TODO: Assert that elem size = elemSize + def push(elem: Array[Int]): Unit = { + this.array = this.array ++ elem + this.length += 1 + } + } + + // === VariantMatcher === + + /** A utility for generating unapply and match methods for a [[Component]] + * that contains a sum type. + * + * It is used internally by the [[org.enso.graph.definition.Macro.field]] + * macro to autogenerate matchers for variant fields. + */ + case class VariantMatcher[T <: Component.Field, V](ix: Int) { + def unapply[G <: Graph, C <: Component]( + arg: Component.Ref[G, C] + )( + implicit graph: GraphData[G], + ev: HasComponentField[G, C, T] + ): Option[Component.Refined[T, V, Component.Ref[G, C]]] = { + val variantIndexByteOffset = 0 + if (graph.unsafeReadField[C, T](arg.ix, variantIndexByteOffset) == ix) + Some(Component.Refined[T, V, Component.Ref[G, C]](arg)) + else None + } + } + } + + // ================= + // === GraphData === + // ================= + + /** [[GraphData]] is the underlying storage representation used by the + * [[Graph]]. + * + * It contains the raw data for all the graph components and their fields. + * + * @param info information about the graph's underlying structure + * @tparam G the graph type that the data is for + */ + final class GraphData[G <: Graph]()(implicit val info: GraphInfo[G]) { + var components: Array[Component.Storage] = + this.componentSizes.map(size => new Component.Storage(size)).to[Array] + + def unsafeGetFieldData[C <: Component, F <: Component.Field]( + componentIx: Int, + fieldIx: Int + )(implicit info: HasComponentField[G, C, F]): (Array[Int], Int) = { + val arr = components(info.componentIndex).array + val idx = info.componentSize * componentIx + info.fieldOffset + fieldIx + (arr, idx) + } + + def unsafeReadField[C <: Component, F <: Component.Field]( + componentIx: Int, + fieldIx: Int + )(implicit ev: HasComponentField[G, C, F]): Int = { + val (arr, idx) = unsafeGetFieldData(componentIx, fieldIx) + arr(idx) + } + + def unsafeWriteField[C <: Component, F <: Component.Field]( + componentIx: Int, + fieldIx: Int, + value: Int + )(implicit ev: HasComponentField[G, C, F]): Unit = { + val (arr, idx) = unsafeGetFieldData(componentIx, fieldIx) + arr(idx) = value + } + + def addComponent[C <: Component]()( + implicit info: HasComponent[G, C] + ): Component.Ref[G, C] = { + val compClsIx = info.componentIndex + val compIx = components(compClsIx).length + val data = new Array[Int](info.componentSize) + components(compClsIx).push(data) + Component.Ref(compIx) + } + } + object GraphData { + implicit def getInfo[G <: Graph](g: GraphData[G]): GraphInfo[G] = g.info + } + + // ==================== + // === TypeFamilies === + // ==================== + + // === GraphInfo === + + /** Information about the number and sizes of components stored in the graph. + * + * @tparam G the graph for which this metadata exists + */ + trait GraphInfo[G <: Graph] { + val componentCount: Int + val componentSizes: Vector[Int] + } + object GraphInfo { + implicit def instance[ + G <: Graph, + ComponentList <: HList, + ComponentSizeList >: HList, + ComponentListLength <: Nat + ]( + implicit + ev1: Component.List.Aux[G, ComponentList], + ev2: hlist.Length.Aux[ComponentList, ComponentListLength], + componentSizesEv: ComponentListToSizes[G, ComponentList], + len: nat.ToInt[ComponentListLength] + ): GraphInfo[G] = new GraphInfo[G] { + val componentCount = len() + val componentSizes = componentSizesEv.sizes + } + } + + // === HasComponent === + + /** Encodes that a given graph [[G]] has a component with given type [[C]]. + * + * @tparam G the graph type + * @tparam C the component type + */ + trait HasComponent[G <: Graph, C <: Component] { + val componentIndex: Int + val componentSize: Int + } + object HasComponent { + implicit def instance[ + G <: Graph, + C <: Component, + ComponentList <: HList, + PrevComponentList <: HList, + ComponentIndex <: Nat, + FieldList <: HList + ]( + implicit + ev1: Component.List.Aux[G, ComponentList], + ev2: Component.Field.List.Aux[G, C, FieldList], + ev3: HListTakeUntil.Aux[C, ComponentList, PrevComponentList], + ev4: hlist.Length.Aux[PrevComponentList, ComponentIndex], + componentIndexEv: nat.ToInt[ComponentIndex], + componentSizeEv: KnownSize[FieldList] + ): HasComponent[G, C] = new HasComponent[G, C] { + val componentIndex = componentIndexEv() + val componentSize = componentSizeEv.asInt + } + } + + // === HasComponentField === + + /** Encodes that a graph [[G]] has field [[F]] in component [[C]]. + * + * @tparam G the graph type + * @tparam C the component type in [[G]] + * @tparam F the field type in [[C]] + */ + trait HasComponentField[G <: Graph, C <: Component, F <: Component.Field] { + val componentIndex: Int + val componentSize: Int + val fieldOffset: Int + } + object HasComponentField { + implicit def instance[ + G <: Graph, + C <: Component, + F <: Component.Field, + FieldList <: HList + ]( + implicit + ev1: Component.Field.List.Aux[G, C, FieldList], + evx: HasComponent[G, C], + fieldOffsetEv: SizeUntil[F, FieldList] + ): HasComponentField[G, C, F] = new HasComponentField[G, C, F] { + val componentIndex = evx.componentIndex + val componentSize = evx.componentSize + val fieldOffset = fieldOffsetEv.asInt + } + } + + // === ComponentListToSizes === + + /** Obtains the sizes of all the components from the graph's list of + * components. + * + * @tparam G the graph + * @tparam ComponentList the list of components + */ + trait ComponentListToSizes[G <: Graph, ComponentList <: HList] { + val sizes: Vector[Int] + } + object ComponentListToSizes { + implicit def onNil[G <: Graph]: ComponentListToSizes[G, HNil] = + new ComponentListToSizes[G, HNil] { val sizes = Vector[Int]() } + + implicit def onCons[G <: Graph, C <: Component, Tail <: HList]( + implicit + tail: ComponentListToSizes[G, Tail], + info: HasComponent[G, C] + ): ComponentListToSizes[G, C :: Tail] = + new ComponentListToSizes[G, C :: Tail] { + val sizes = info.componentSize +: tail.sizes + } + } +} diff --git a/common/scala/graph/src/main/scala/org/enso/graph/definition/Macro.scala b/common/scala/graph/src/main/scala/org/enso/graph/definition/Macro.scala new file mode 100644 index 00000000000..34326201556 --- /dev/null +++ b/common/scala/graph/src/main/scala/org/enso/graph/definition/Macro.scala @@ -0,0 +1,933 @@ +package org.enso.graph.definition + +import scala.annotation.{compileTimeOnly, StaticAnnotation} +import scala.reflect.macros.whitebox + +object Macro { + + /** This macro generates a field definition for the graph, and can generate + * these definitions for both single and variant fields for a + * [[org.enso.graph.Graph.Component]]. + * + * For a single field, you provide it a definition as follows: + * + * {{{ + * @field case class MyName[TParams..](args...) + * }}} + * + * It will then generate the required boilerplate for this field definition, + * including field setters and getters for each of the constructor arguments + * in the template definition. + * + * As an example, consider the following: + * + * {{{@field case class ParentLink[G <: Graph](parent: Edge[G])}}} + * + * This application of the macro will generate the following code: + * + * {{{ + * sealed case class ParentLink() extends Graph.Component.Field + * object ParentLink { + * implicit def sized = new Sized[ParentLink] { type Out = _1 } + * + * implicit class ParentLinkInstance[G <: Graph, C <: Component]( + * node: Component.Ref[G, C] + * ) { + * def parent( + * implicit graph: GraphData[G], + * ev: HasComponentField[G, C, ParentLink] + * ): Edge[G] = { + * Component.Ref(graph.unsafeReadField[C, ParentLink](node.ix, 0)) + * } + * + * def parent_=(value: Edge[G])( + * implicit graph: GraphData[G], + * ev: HasComponentField[G, C, ParentLink] + * ): Unit = { + * graph.unsafeWriteField[C, ParentLink](node.ix, 0, value.ix) + * } + * } + * + * implicit def ParentLink_transInstance[ + * F <: Component.Field, + * R, + * G <: Graph, + * C <: Component + * ]( + * t: Component.Refined[F, R, Component.Ref[G, C]] + * ): ParentLinkInstance[G, C] = + * t.wrapped + * } + * }}} + * + * You will need to ensure that `T._` is imported into scope as we currently + * have no way of making that work better. + * + * For a variant field (tagged union), the same macro can be applied to an + * object definition. This object definition must be provided as follows: + * + * {{{ + * @field object VariantType { + * case class V1() + * case class V2[T](field1: T) + * case class V3[T, Q](field1: T, field2: Q) + * // ... + * } + * }}} + * + * For this type of definition, the name of the object (here `VariantType`) + * defines the type of the variant, and the case classes in the body define + * the variant cases. Each case is supplied with subfield accessors, as well + * ]as custom unapply methods, where necessary. The fields are nested in the + * scope of the variant, so above you would have `VariantType.V1`, for + * access. + * + * The following is a simple definition of a variant component: + * + * {{{ + * @field object Shape { + * case class Null() + * case class App[G <: Graph](fn: Edge[G], argTest: Edge[G]) + * } + * }}} + * + * This generates the following field definition: + * + * {{{ + * sealed trait Shape extends Graph.Component.Field + * object Shape { + * implicit def sized = new Sized[Shape] { type Out = _3 } + * + * sealed case class Null() extends Shape + * object Null { + * val any = Component.VariantMatcher[Shape, Null](0) + * implicit def sized = new Sized[Null] { type Out = _0 } + * } + * + * sealed case class App() extends Shape + * object App { + * implicit def sized = new Sized[App] { type Out = _1 } + * + * val any = Component.VariantMatcher[Shape, App](1) + * + * def unapply[G <: Graph, C <: Component](arg: Component.Ref[G, C])( + * implicit + * graph: GraphData[G], + * ev: HasComponentField[G, C, Shape] + * ): Option[(Edge[G], Edge[G])] = + * any.unapply(arg).map(t => (t.fn, t.arg)) + * + * implicit class AppInstance[G <: Graph, C <: Component]( + * node: Component.Refined[Shape, App, Component.Ref[G, C]] + * ) { + * + * def fn( + * implicit graph: GraphData[G], + * ev: HasComponentField[G, C, Shape] + * ): Edge[G] = { + * Component.Ref( + * graph + * .unsafeReadField[C, Shape]( + * Component.Refined.unwrap(node).ix, + * 1 + * ) + * ) + * } + * + * def fn_=(value: Edge[G])( + * implicit graph: GraphData[G], + * ev: HasComponentField[G, C, Shape] + * ): Unit = { + * graph.unsafeWriteField[C, Shape]( + * Component.Refined.unwrap(node).ix, + * 1, + * value.ix + * ) + * } + * + * def arg( + * implicit graph: GraphData[G], + * ev: HasComponentField[G, C, Shape] + * ): Edge[G] = { + * Component.Ref( + * graph + * .unsafeReadField[C, Shape]( + * Component.Refined.unwrap(node).ix, + * 2 + * ) + * ) + * } + * + * def arg_=(value: Edge[G])( + * implicit graph: GraphData[G], + * ev: HasComponentField[G, C, Shape] + * ): Unit = { + * graph.unsafeWriteField[C, Shape]( + * Component.Refined.unwrap(node).ix, + * 2, + * value.ix + * ) + * } + * } + * } + * } + * }}} + */ + @compileTimeOnly("please enable macro paradise to expand macro annotations") + class field extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = macro FieldMacro.impl + } + object FieldMacro { + def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { + import c.universe._ + val members = annottees.map(_.tree).toList + + if (members.size != 1) { + c.error( + c.enclosingPosition, + "You must apply the @field annotation to a single entity" + ) + } + + val imports: Block = Block( + List( + q"""import shapeless.nat._""", + q"""import org.enso.graph.Graph.Component""", + q"""import org.enso.graph.Graph.GraphData""", + q"""import org.enso.graph.Graph.HasComponentField""" + ), + EmptyTree + ) + + /** Extracts the template from a type definition. + * + * The template contains the body of the type definition, as well as the + * definition of its constructor. + * + * @param classDef the class definition + * @return the class template, if it exists + */ + def extractDefTemplate(classDef: ClassDef): Option[Template] = { + for (elem <- classDef.children) { + elem match { + case template: Template => return Some(template) + case _ => + } + } + None + } + + /** Extracts the constructor arguments from the class definition. + * + * @param classDef the class definition + * @return a list containing the constructor arguments from `classDef` + */ + def extractConstructorArguments(classDef: ClassDef): List[ValDef] = { + val mTemplate = extractDefTemplate(classDef) + + mTemplate match { + case Some(template) => { + val allVals = template.body.collect { + case valDef: ValDef => valDef + } + + allVals.filter( + t => + t.mods.hasFlag( + c.universe.Flag.CASEACCESSOR | c.universe.Flag.PARAMACCESSOR + ) + ) + } + case _ => List() + } + } + + /** Appends a statement to a block with no return value. + * + * @param block the block to append to + * @param statement the statement to append to `block` + * @return `block` with `statement` appended to it + */ + def appendToBlock(block: Block, statement: Tree*): Block = Block( + block.stats ++ statement, + EmptyTree + ) + + /** Creates a constant type-level identifier for Shapeless' + * [[shapeless.Nat]] type. + * + * @param num the natural number you want to represent as a + * [[shapeless.Nat]] + * @return a [[TypeName]] representing that [[shapeless.Nat]] + */ + def mkNatConstantTypeName(num: Int): TypeName = + TypeName("_" + num.toString) + + /** Generates a getter for an element of a non-variant field. + * + * @param paramDef the definition of the subfield + * @param enclosingTypeName the name of the field type + * @param index the index of this subfield in the field + * @return a definition for this subfield getter + */ + def genSimpleSubfieldGetter( + paramDef: ValDef, + enclosingTypeName: TypeName, + index: Int + ): Tree = { + val paramName: TermName = paramDef.name + val paramType: Tree = paramDef.tpt + + q""" + def $paramName( + implicit graph: GraphData[G], + ev: HasComponentField[G, C, $enclosingTypeName] + ): $paramType = { + Component.Ref( + graph.unsafeReadField[C, $enclosingTypeName](node.ix, $index) + ) + } + """ + } + + /** Generates a setter for an element of a non-variant field. + * + * @param paramDef the definition of the subfield + * @param enclosingTypeName the name of the field type + * @param index the index of this subfield in the field + * @return a definition for this subfield setter + */ + def genSimpleSubfieldSetter( + paramDef: ValDef, + enclosingTypeName: TypeName, + index: Int + ): Tree = { + val accessorName: TermName = TermName(paramDef.name.toString + "_$eq") + val paramType: Tree = paramDef.tpt + + q""" + def $accessorName(value: $paramType)( + implicit graph: GraphData[G], + ev: HasComponentField[G, C, $enclosingTypeName] + ): Unit = { + graph.unsafeWriteField[C, $enclosingTypeName]( + node.ix, $index, value.ix + ) + } + """ + } + + /** Generates setters and getters for all the subfields for a given + * non-variant field. + * + * @param subfields a list containing the subfield definitions + * @param enclosingName the name of the field type + * @return the definitions of getters and setters for all the subfields + * in `subfields` + */ + def genSimpleSubfieldAccessors( + subfields: List[ValDef], + enclosingName: TypeName + ): List[Tree] = { + var accessorDefs: List[Tree] = List() + + for ((subfield, ix) <- subfields.view.zipWithIndex) { + accessorDefs = accessorDefs :+ genSimpleSubfieldGetter( + subfield, + enclosingName, + ix + ) + accessorDefs = accessorDefs :+ genSimpleSubfieldSetter( + subfield, + enclosingName, + ix + ) + } + + accessorDefs + } + + /** Generates an instance that is used for assisting inference with + * refined subfields. + * + * @param enclosingName the name of the field + * @param implicitClassName the name of the instance + * @return an instance definition that assists with inference + */ + def genTransInstance( + enclosingName: TermName, + implicitClassName: TypeName + ): Tree = { + val defName = TermName(enclosingName.toString + "_transInstance") + + q""" + implicit def $defName[ + F <: Component.Field, + R, + G <: Graph, + C <: Component + ]( + t: Component.Refined[F, R, Component.Ref[G, C]] + ): $implicitClassName[G, C] = t.wrapped + """ + } + + /** Appends trees to a template definition. + * + * @param template the template to append to + * @param trees the trees to append + * @return `template` with `trees` appended to the end of its body + */ + def appendToTemplate(template: Template, trees: List[Tree]): Template = { + Template( + template.parents, + template.self, + template.body ++ trees + ) + } + + /** Appends trees to the body of a class definition. + * + * @param classDef the definition to append to + * @param trees the trees to append + * @return `classDef` with `trees` appended to the end of its body + */ + def appendToClass(classDef: ClassDef, trees: List[Tree]): ClassDef = { + ClassDef( + classDef.mods, + classDef.name, + classDef.tparams, + appendToTemplate(classDef.impl, trees) + ) + } + + /** Appends trees to the body of a module (object) definition. + * + * @param module the definition to append to + * @param trees the trees to append + * @return `module` with `trees` appended to the end of its body + */ + def appendToModule(module: ModuleDef, trees: List[Tree]): ModuleDef = { + ModuleDef( + Modifiers(), + module.name, + appendToTemplate(module.impl, trees) + ) + } + + /** Generates the name for the implicit class containing the accessors. + * + * @param typeName the name of the field + * @return the name of the field's implicit accessor class + */ + def mkImplicitClassName(typeName: TypeName): TypeName = + TypeName(typeName.toString + "Instance") + + /** Generates a set of definitions that correspond to defining a + * non-variant field for a graph component. + * + * @param classDef the class definition to expand + * @return a full definition for the field + */ + def processSingleField(classDef: ClassDef): c.Expr[Any] = { + val fieldTypeName: TypeName = classDef.name + val fieldTermName: TermName = fieldTypeName.toTermName + val subfields: List[ValDef] = extractConstructorArguments(classDef) + val natSubfields: TypeName = mkNatConstantTypeName(subfields.length) + val implicitClassName: TypeName = mkImplicitClassName(fieldTypeName) + + val baseClass: Tree = + q"sealed case class $fieldTypeName() extends Graph.Component.Field" + + val accessorClassStub: ClassDef = q""" + implicit class $implicitClassName[G <: Graph, C <: Component]( + node: Component.Ref[G, C] + ) + """.asInstanceOf[ClassDef] + val accessorClass: ClassDef = appendToClass( + accessorClassStub, + genSimpleSubfieldAccessors(subfields, fieldTypeName) + ) + + val companionModuleStub: ModuleDef = + q""" + object $fieldTermName { + implicit def sized = new Sized[$fieldTypeName] { + type Out = $natSubfields + } + } + """.asInstanceOf[ModuleDef] + + val companionModule = + appendToModule( + companionModuleStub, + List( + accessorClass, + genTransInstance(fieldTermName, implicitClassName) + ) + ) + + val resultBlock = + appendToBlock(imports, baseClass, companionModule).stats + + val result = q"..$resultBlock" + + c.Expr(result) + } + + /** Generates a getter for an element of a variant + * + * @param paramDef the definition of the subfield + * @param enclosingTypeName the name of the field type + * @param index the index of this subfield in the field + * @return a definition for this subfield getter + */ + def genVariantSubfieldGetter( + paramDef: ValDef, + enclosingTypeName: TypeName, + index: Int + ): Tree = { + val paramName: TermName = paramDef.name + val paramType: Tree = paramDef.tpt + + q""" + def $paramName( + implicit graph: GraphData[G], + ev: HasComponentField[G, C, $enclosingTypeName] + ): $paramType = { + Component.Ref( + graph.unsafeReadField[C, $enclosingTypeName]( + Component.Refined.unwrap(node).ix, + $index + ) + ) + } + """ + } + + /** Generates a setter for an element of a variant field. + * + * @param paramDef the definition of the subfield + * @param enclosingTypeName the name of the field type + * @param index the index of this subfield in the field + * @return a definition for this subfield setter + */ + def genVariantSubfieldSetter( + paramDef: ValDef, + enclosingTypeName: TypeName, + index: Int + ): Tree = { + val accessorName: TermName = TermName(paramDef.name.toString + "_$eq") + val paramType: Tree = paramDef.tpt + + q""" + def $accessorName(value: $paramType)( + implicit graph: GraphData[G], + ev: HasComponentField[G, C, $enclosingTypeName] + ): Unit = { + graph.unsafeWriteField[C, $enclosingTypeName]( + Component.Refined.unwrap(node).ix, + $index, + value.ix + ) + } + """ + } + + /** Generates setters and getters for all the subfields for a given + * variant field. + * + * @param subfields a list containing the subfield definitions + * @param enclosingName the name of the field type + * @return the definitions of getters and setters for all the subfields + * in `subfields` + */ + def genVariantSubfieldAccessors( + subfields: List[ValDef], + enclosingName: TypeName + ): List[Tree] = { + var accessorDefs: List[Tree] = List() + + for ((subfield, ix) <- subfields.view.zipWithIndex) { + accessorDefs = accessorDefs :+ genVariantSubfieldGetter( + subfield, + enclosingName, + ix + ) + accessorDefs = accessorDefs :+ genVariantSubfieldSetter( + subfield, + enclosingName, + ix + ) + } + + accessorDefs + } + + /** Extracts the variant case definitions from the module body. + * + * @param template the template of the variant defining module + * @return a list of the variant cases + */ + def extractVariantDefs(template: Template): List[ClassDef] = { + template.body.collect { case classDef: ClassDef => classDef } + } + + /** Generates the definition body for a variant case. + * + * @param classDef the definition of the variant case + * @param parentName the name of the parent variant + * @param index the case's index in the variant + * @return the expanded defintion for the variant case described by + * `classDef` + */ + def generateVariantCaseDef( + classDef: ClassDef, + parentName: TypeName, + index: Int + ): (Block, Int) = { + val typeName = classDef.name + val termName = typeName.toTermName + val subfields = extractConstructorArguments(classDef) + val natSubfields = mkNatConstantTypeName(subfields.length) + val implicitClassName = mkImplicitClassName(typeName) + val subfieldTypes = subfields.map(f => f.tpt) + + val variantClass: ClassDef = + q""" + sealed case class $typeName() extends $parentName + """.asInstanceOf[ClassDef] + + val variantModuleStub: ModuleDef = + q""" + object $termName { + val any = Component.VariantMatcher[$parentName, $typeName]($index) + implicit def sized = new Sized[$typeName] { + type Out = $natSubfields + } + } + """.asInstanceOf[ModuleDef] + + val unapplyDef: DefDef = + q""" + def unapply[G <: Graph, C <: Component](arg: Component.Ref[G, C])( + implicit + graph: GraphData[G], + ev: HasComponentField[G, C, $parentName] + ): Option[(..$subfieldTypes)] = { + any.unapply(arg).map( + t => (..${subfields.map(f => q"t.${f.name}")}) + ) + } + """.asInstanceOf[DefDef] + + val accessorClassStub: ClassDef = q""" + implicit class $implicitClassName[G <: Graph, C <: Component]( + node: Component.Refined[ + $parentName, + $typeName, + Component.Ref[G, C] + ] + ) + """.asInstanceOf[ClassDef] + val accessorClass: ClassDef = appendToClass( + accessorClassStub, + genVariantSubfieldAccessors(subfields, parentName) + ) + + val result = if (subfields.nonEmpty) { + appendToModule(variantModuleStub, List(unapplyDef, accessorClass)) + } else { + variantModuleStub + } + + ( + q"..${List(variantClass, result)}".asInstanceOf[Block], + subfields.length + ) + } + + /** Generates the whole set of definitions necessary for a variant + * component field from the provided description. + * + * @param moduleDef the description of the variant field + * @return the expanded defintion of the variant described by `moduleDef` + */ + def processVariantField(moduleDef: ModuleDef): c.Expr[Any] = { + val variantTermName: TermName = moduleDef.name + val variantTypeName: TypeName = variantTermName.toTypeName + + val baseTrait: ClassDef = + q""" + sealed trait $variantTypeName extends Graph.Component.Field + """.asInstanceOf[ClassDef] + + val variantDefs = extractVariantDefs(moduleDef.impl) + + if (variantDefs.length < 1) { + c.error( + c.enclosingPosition, + "A variant must contain at least one case" + ) + } + + val variantResults = + for ((cls, ix) <- variantDefs.view.zipWithIndex) + yield generateVariantCaseDef(cls, variantTypeName, ix) + + // We want to flatten the block structure for correct codegen + val variantDefinitions = + variantResults.map(_._1).map(t => t.stats).flatten + + // Note [Encoding Variant Size] + val numTotalSize = variantResults + .map(_._2) + .foldLeft(0)((x: Int, y: Int) => Math.max(x, y)) + 1 + + val baseModuleStub: ModuleDef = + q""" + object $variantTermName { + implicit def sized = new Sized[$variantTypeName] { + type Out = ${mkNatConstantTypeName(numTotalSize)} + } + } + """.asInstanceOf[ModuleDef] + + val traitCompanionModule = + appendToModule(baseModuleStub, variantDefinitions.toList) + + val result = + q"..${appendToBlock(imports, baseTrait, traitCompanionModule).stats}" + + c.Expr(result) + } + + /* Note [Encoding Variant Size] + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Variant fields are encoded on the graph much like a union is in C. The + * space allocation for storing the type is always the maximum size of any + * of the variant types. + * + * However, the encoding used by the graphs operates more like a _tagged_ + * union, and hence we need to encode the constructor of the variant that + * is stored in its allocation. + * + * To this end, the size of the variant type is _actually_ the maximum + * size of all the variant constructors + 1, where that additional + * field is used to encode the constructor tag. + */ + + members.head match { + case classDef: ClassDef => { + val modifiers: Modifiers = classDef.mods + + if (!modifiers.hasFlag(c.universe.Flag.CASE)) { + c.error( + c.enclosingPosition, + "@field must be applied to a case class or object" + ) + annottees.head + } else { + processSingleField(classDef) + } + } + case moduleDef: ModuleDef => { + processVariantField(moduleDef) + } + case _ => { + c.error( + c.enclosingPosition, + "The @field macro only operates on case classes" + ) + annottees.head + } + } + } + } + + /** This macro generates a component definition for the graph to help avoid + * the boilerplate necessary to defined a [[org.enso.graph.Graph.Component]]. + * + * You must apply the macro to a definition as follows: + * + * {{{ + * @component case class Types() { type Type[G <: Graph] } + * }}} + * + * The macro will generate the required boilerplate for the component + * definition. It will generate a parent class named `Types`, and the a + * contained type with name taken from the first type definition in the body. + * That type must take one type parameter that is a subtype of Graph. + * + * By way of example, consider the following macro invocation: + * + * {{{ + * @component case class Nodes() { type Node[G <: Graph] } + * }}} + * + * This will generate the following code (along with required imports): + * + * {{{ + * sealed case class Nodes() extends Component + * type Node[G <: Graph] = Component.Ref[G, Nodes] + * implicit class GraphWithNodes[G <: Graph](graph: GraphData[G]) { + * def addNode()(implicit ev: HasComponent[G, Nodes]): Node[G] = { + * graph.addComponent[Nodes]() + * } + * } + * }}} + * + */ + @compileTimeOnly("please enable macro paradise to expand macro annotations") + class component extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = macro ComponentMacro.impl + } + object ComponentMacro { + def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { + import c.universe._ + val members = annottees.map(_.tree).toList + + if (members.size != 1) { + c.error( + c.enclosingPosition, + "You must apply the @component annotation to a single entity" + ) + } + + val imports: Block = Block( + List( + q"""import org.enso.graph.Graph""", + q"""import org.enso.graph.Graph.Component""", + q"""import org.enso.graph.Graph.GraphData""", + q"""import org.enso.graph.Graph.HasComponent""" + ), + EmptyTree + ) + + /** + * Appends a statement to a block with no return value. + * + * @param block the block to append to + * @param statement the statement to append to `block` + * @return `block` with `statement` appended to it + */ + def appendToBlock(block: Block, statement: Tree*): Block = Block( + block.stats ++ statement, + EmptyTree + ) + + /** + * Extracts the name of the child type in the definition. This name is + * used to define the type contained in a given component. + * + * If multiple `type T` definitions are found, the first will be used. + * + * @param template the body of the class the macro is applied to + * @return the name of the type declared in the body of `template` + */ + def extractItemName(template: Template): TypeName = { + val typeDefs = template.body.collect { case tDef: TypeDef => tDef } + val boundName = TypeName("Graph") + + if (typeDefs.isEmpty) { + c.error( + c.enclosingPosition, + "You must provide a name for the contained type, none found" + ) + TypeName("ERROR") + } else { + val tDef = typeDefs.head + val typeParams = tDef.children.collect { + case typeDef: TypeDef => typeDef + } + + if (typeParams.length == 1) { + val boundNames = typeParams.head.children + .collect { + case tree: TypeBoundsTree => tree + } + .map(_.hi) + .collect { + case Ident(name) => name.toTypeName + } + + if (boundNames.contains(boundName)) { + tDef.name + } else { + c.error( + c.enclosingPosition, + "The contained type's parameter must be a subtype of Graph" + ) + tDef.name + } + } else { + c.error( + c.enclosingPosition, + "Your contained type must only have one type parameter" + ) + tDef.name + } + } + } + + /** Generates a graph component from the provided description. + * + * @param classDef a description of the variant component + * @return the expanded form of the variant described by `classDef` + */ + def genComponentFromClassDef(classDef: ClassDef): c.Expr[Any] = { + if (!classDef.mods.hasFlag(Flag.CASE)) { + c.error( + c.enclosingPosition, + "@component must be applied to a case class" + ) + } + + val componentTypeName = classDef.name + val componentItemName = extractItemName(classDef.impl) + + val caseClass: ClassDef = + q""" + sealed case class $componentTypeName() extends Component + """.asInstanceOf[ClassDef] + + val typeDef: TypeDef = + q""" + type $componentItemName[G <: Graph] = + Component.Ref[G, $componentTypeName] + """.asInstanceOf[TypeDef] + + val implClassName = TypeName("GraphWith" + componentTypeName.toString) + val addName = TermName("add" + componentItemName.toString) + val implicitClass: ClassDef = + q""" + implicit class $implClassName[G <: Graph](graph: GraphData[G]) { + def $addName()( + implicit ev: HasComponent[G, $componentTypeName] + ): $componentItemName[G] = { + graph.addComponent[$componentTypeName]() + } + } + """.asInstanceOf[ClassDef] + + val block = appendToBlock(imports, caseClass, typeDef, implicitClass) + val result = q"..${block.stats}" + + c.Expr(result) + } + + val annotatedItem = members.head + + annotatedItem match { + case classDef: ClassDef => genComponentFromClassDef(classDef) + case _ => { + c.error( + c.enclosingPosition, + "You must provide a class definition to the @component macro" + ) + c.Expr(annotatedItem) + } + } + } + } +} diff --git a/common/scala/graph/src/test/scala/org/enso/graph/ComponentMacroTest.scala b/common/scala/graph/src/test/scala/org/enso/graph/ComponentMacroTest.scala new file mode 100644 index 00000000000..8e8a1f0282a --- /dev/null +++ b/common/scala/graph/src/test/scala/org/enso/graph/ComponentMacroTest.scala @@ -0,0 +1,42 @@ +package org.enso.graph + +import org.enso.graph.definition.Macro.component +import org.scalatest.{FlatSpec, Matchers} +import shapeless.test.illTyped + +class ComponentMacroTest extends FlatSpec with Matchers { + + "The `@component` macro" should "define correct components" in { + "@component case class Nodes() { type Node[G <: Graph] }" should compile + "@component case class Edges() { type Edge[G <: Graph] }" should compile + } + + "The `@component` macro child type" should "be accessible" in { + "@component case class Nodes() { type Node[G <: Graph] }\n type Test = Node[Graph]" should compile + } + + "The `@component` macro child type" must "error when its type parameter does not subclass graph" in { + illTyped( + "@component case class Nodes() { type Node[G] }", + "The contained type's parameter must be a subtype of Graph" + ) + } + + "The `@component` child type" must "not allow more than one type parameter" in { + illTyped( + "@component case class Nodes() { type Node[G <: Graph, C] }", + "Your contained type must only have one type parameter" + ) + } + + "The `@component` macro" should "only allow application to case classes" in { + illTyped ( + "@component object Test", + "You must provide a class definition to the @component macro" + ) + illTyped ( + "@component class Foo()", + "@component must be applied to a case class" + ) + } +} diff --git a/common/scala/graph/src/test/scala/org/enso/graph/FieldMacroTest.scala b/common/scala/graph/src/test/scala/org/enso/graph/FieldMacroTest.scala new file mode 100644 index 00000000000..98571b3bf9b --- /dev/null +++ b/common/scala/graph/src/test/scala/org/enso/graph/FieldMacroTest.scala @@ -0,0 +1,45 @@ +package org.enso.graph + +import org.enso.graph.definition.Macro.{component, field} +import org.scalatest.{FlatSpec, Matchers} +import shapeless.test.illTyped + +class FieldMacroTest extends FlatSpec with Matchers { + + // == Components for Tests ================================================== + @component case class Nodes() { type Node[G <: Graph] } + @component case class Edges() { type Edge[G <: Graph] } + + "The `@field` macro" should "work for single fields" in { + "@field case class Shape[G <: Graph](source: Node[G], target: Node[G])" should compile + } + + "The `@field` macro" should "work for variant fields" in { + "@field object Shape {\n case class Null()\n case class App[G <: Graph](fn: Edge[G], argTest: Edge[G])}" should compile + } + + "Access to companion objects for fields" should "work as expected" in { + "@field object Shape {\n case class Null()\n case class App[G <: Graph](fn: Edge[G], argTest: Edge[G])}\nval test = Shape.Null.any" should compile + } + + "The `@field` macro" should "not allow application to non case classes" in { + illTyped( + "@field class Shape", + "@field must be applied to a case class or object" + ) + } + + "The `@field` macro" should "not allow application to invalid constructs" in { + illTyped( + "@field type foo", + "The @field macro only operates on case classes" + ) + } + + "The `@field` macro" should "error on variants without branches" in { + illTyped( + "@field object Shape{}", + "A variant must contain at least one case" + ) + } +} diff --git a/common/scala/graph/src/test/scala/org/enso/graph/GraphTest.scala b/common/scala/graph/src/test/scala/org/enso/graph/GraphTest.scala new file mode 100644 index 00000000000..f4fe6bb71ef --- /dev/null +++ b/common/scala/graph/src/test/scala/org/enso/graph/GraphTest.scala @@ -0,0 +1,126 @@ +package org.enso.graph + +import org.enso.graph.Graph.Component +import org.enso.graph.definition.Macro.{component, field} +import org.scalatest.{FlatSpec, Matchers} +import shapeless.{::, HNil} + +class GraphTest extends FlatSpec with Matchers { + object GraphImpl { + + // ======================================================================== + // === Component Definitions ============================================== + // ======================================================================== + + // === Node === + @component case class Nodes() { type Node[G <: Graph] } + + // === Edge === + @component case class Edges() { type Edge[G <: Graph] } + + // ======================================================================== + // === Component Field Definitions ======================================== + // ======================================================================== + + object Node { + + // === Node Shape === + @field object Shape { + case class Null() + case class App[G <: Graph](fn: Edge[G], argTest: Edge[G]) + } + + // === ParentLink === + @field case class ParentLink[G <: Graph](parent: Edge[G]) + } + + object Edge { + + // === Edge Shape === + @field case class Shape[G <: Graph](source: Node[G], target: Node[G]) + } + + // ======================================================================== + // === Example Graph Implementation ======================================= + // ======================================================================== + + case class MyGraph() extends Graph + + implicit def components = new Graph.Component.List[MyGraph] { + type Out = Nodes :: Edges :: HNil + } + + implicit def nodeFields = new Graph.Component.Field.List[MyGraph, Nodes] { + type Out = Node.Shape :: Node.ParentLink :: HNil + } + + implicit def edgeFields = new Graph.Component.Field.List[MyGraph, Edges] { + type Out = Edge.Shape :: HNil + } + } + + // ========================================================================== + // === Example Graph Usage ================================================== + // ========================================================================== + + import GraphImpl.Edge.Shape._ + import GraphImpl.Node.ParentLink._ + import GraphImpl.Node.Shape.App._ + import GraphImpl._ + + implicit val graph = Graph[GraphImpl.MyGraph](); + + val n1: Node[MyGraph] = graph.addNode() + val n2: Node[MyGraph] = graph.addNode() + val n3: Node[MyGraph] = graph.addNode() + + val e1: Edge[MyGraph] = graph.addEdge() + e1.source = n1 + e1.target = n2 + + n1.parent = Component.Ref(1) + n2.parent = Component.Ref(2) + n3.parent = Component.Ref(3) + + // This is just dirty and very unsafe way of changing `n1` to be App! + graph.unsafeWriteField[Nodes, GraphImpl.Node.Shape](n1.ix, 0, 1) + + // ========================================================================== + // === Tests ================================================================ + // ========================================================================== + + "Matching on variants" should "work properly" in { + val typeResult = n1 match { + case GraphImpl.Node.Shape.Null.any(n @ _) => "Null" + case GraphImpl.Node.Shape.App.any(n1 @ _) => "App1" + case GraphImpl.Node.Shape.App(_, _) => "App2" + } + + typeResult shouldEqual "App1" + } + + "Matching on variants" should "refine the variant type" in { + val refinedResult = n1 match { + case GraphImpl.Node.Shape.App.any(n1) => n1.fn + } + + refinedResult shouldEqual 1 + } + + "Component fields" can "be accessed properly" in { + e1.source shouldEqual n1 + e1.target shouldEqual n2 + } + + "The graph" should "be mutable" in { + val e2: Edge[MyGraph] = graph.addEdge() + e2.source = n1 + e2.target = n2 + + e2.source shouldEqual n1 + + e2.source = n3 + + e2.source shouldEqual n3 + } +} diff --git a/common/scala/graph/src/test/scala/org/enso/graph/TypeFunctionTest.scala b/common/scala/graph/src/test/scala/org/enso/graph/TypeFunctionTest.scala new file mode 100644 index 00000000000..1cf77aeb218 --- /dev/null +++ b/common/scala/graph/src/test/scala/org/enso/graph/TypeFunctionTest.scala @@ -0,0 +1,48 @@ +package org.enso.graph + +import shapeless.{::, HNil} +import shapeless.Nat._ + +object TypeFunctionTest { + + object HListSumTest { + implicitly[HListSum.Aux[HNil, _0]] + implicitly[HListSum.Aux[_1 :: HNil, _1]] + implicitly[HListSum.Aux[_1 :: _2 :: HNil, _3]] + implicitly[HListSum.Aux[_1 :: _2 :: _3 :: HNil, _6]] + } + + object HListTakeUntilTest { + case class A() + case class B() + case class C() + implicitly[HListTakeUntil.Aux[A, HNil, HNil]] + implicitly[HListTakeUntil.Aux[A, A :: B :: C :: HNil, HNil]] + implicitly[HListTakeUntil.Aux[B, A :: B :: C :: HNil, A :: HNil]] + implicitly[HListTakeUntil.Aux[C, A :: B :: C :: HNil, A :: B :: HNil]] + } + + object MapSizedTest { + case class A() + case class B() + case class C() + implicit def sizedA = new Sized[A] { type Out = _1 } + implicit def sizedB = new Sized[B] { type Out = _3 } + implicit def sizedC = new Sized[C] { type Out = _5 } + implicitly[MapSized.Aux[HNil, HNil]] + implicitly[MapSized.Aux[A :: B :: C :: HNil, _1 :: _3 :: _5 :: HNil]] + } + + object SizeUntilTest { + case class A() + case class B() + case class C() + implicit def sizedA = new Sized[A] { type Out = _1 } + implicit def sizedB = new Sized[B] { type Out = _3 } + implicit def sizedC = new Sized[C] { type Out = _5 } + implicitly[SizeUntil.Aux[A, HNil, _0]] + implicitly[SizeUntil.Aux[A, A :: B :: C :: HNil, _0]] + implicitly[SizeUntil.Aux[B, A :: B :: C :: HNil, _1]] + implicitly[SizeUntil.Aux[C, A :: B :: C :: HNil, _4]] + } +} diff --git a/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala b/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala index 365bf7be1d2..9e21c4c8898 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala @@ -3,7 +3,7 @@ package org.enso.compiler import com.oracle.truffle.api.TruffleFile import com.oracle.truffle.api.source.Source import org.enso.compiler.generate.AstToIr -import org.enso.compiler.ir.IR +import org.enso.compiler.core.IR import org.enso.flexer.Reader import org.enso.interpreter.AstExpression import org.enso.interpreter.Constants @@ -17,8 +17,7 @@ import org.enso.interpreter.runtime.Module import org.enso.interpreter.runtime.error.ModuleDoesNotExistException import org.enso.interpreter.runtime.scope.LocalScope import org.enso.interpreter.runtime.scope.ModuleScope -import org.enso.syntax.text.AST -import org.enso.syntax.text.Parser +import org.enso.syntax.text.{AST, Parser} import scala.collection.JavaConverters._ import scala.collection.mutable @@ -46,6 +45,11 @@ class Compiler( * executable functionality in the module corresponding to `source`. */ def run(source: Source, scope: ModuleScope): ExpressionNode = { + /* TODO [AA] Introduce this next task + * val parsedAST: AST = parse(source) + * val convertedIR: ExpressionNode = translate(parsedAST) + */ + val parsed = new EnsoParser().parseEnso(source.getCharacters.toString) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/ir/IR.scala b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala similarity index 98% rename from engine/runtime/src/main/scala/org/enso/compiler/ir/IR.scala rename to engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala index a241449fea8..a5dbfef1af3 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/ir/IR.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala @@ -1,6 +1,6 @@ -package org.enso.compiler.ir +package org.enso.compiler.core -import org.enso.compiler.ir.IR.Literal.Text +import org.enso.compiler.core.IR.Literal.Text import org.enso.syntax.text.AST import org.enso.syntax.text.ast.text.Escape diff --git a/engine/runtime/src/main/scala/org/enso/compiler/generate/AstToIr.scala b/engine/runtime/src/main/scala/org/enso/compiler/generate/AstToIr.scala index e6c3f7b1a59..e0a3b12d8aa 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/generate/AstToIr.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/generate/AstToIr.scala @@ -1,7 +1,7 @@ package org.enso.compiler.generate import org.enso.compiler.exception.UnhandledEntity -import org.enso.compiler.ir.IR +import org.enso.compiler.core.IR import org.enso.syntax.text.AST /** diff --git a/engine/runtime/src/main/scala/org/enso/compiler/generate/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/generate/IrToTruffle.scala deleted file mode 100644 index df479dfacc2..00000000000 --- a/engine/runtime/src/main/scala/org/enso/compiler/generate/IrToTruffle.scala +++ /dev/null @@ -1,5 +0,0 @@ -package org.enso.compiler.generate - -class IrToTruffle { - -} diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/analysis/Pass.scala b/engine/runtime/src/main/scala/org/enso/interpreter/analysis/Pass.scala deleted file mode 100644 index 620baa46788..00000000000 --- a/engine/runtime/src/main/scala/org/enso/interpreter/analysis/Pass.scala +++ /dev/null @@ -1,5 +0,0 @@ -package org.enso.interpreter.analysis - -class Pass { - -} diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/analysis/StaticAnalyser.scala b/engine/runtime/src/main/scala/org/enso/interpreter/analysis/StaticAnalyser.scala deleted file mode 100644 index dde371a00fa..00000000000 --- a/engine/runtime/src/main/scala/org/enso/interpreter/analysis/StaticAnalyser.scala +++ /dev/null @@ -1,5 +0,0 @@ -package org.enso.interpreter.analysis - -class StaticAnalyser { - -} diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/analysis/desugar/DataTypeDefinition.scala b/engine/runtime/src/main/scala/org/enso/interpreter/analysis/desugar/DataTypeDefinition.scala deleted file mode 100644 index 42bf1fe6eb1..00000000000 --- a/engine/runtime/src/main/scala/org/enso/interpreter/analysis/desugar/DataTypeDefinition.scala +++ /dev/null @@ -1,5 +0,0 @@ -package org.enso.interpreter.analysis.desugar - -class DataTypeDefinition { - -} diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/analysis/desugar/DesugarPhase.scala b/engine/runtime/src/main/scala/org/enso/interpreter/analysis/desugar/DesugarPhase.scala deleted file mode 100644 index 3c906cacd45..00000000000 --- a/engine/runtime/src/main/scala/org/enso/interpreter/analysis/desugar/DesugarPhase.scala +++ /dev/null @@ -1,5 +0,0 @@ -package org.enso.interpreter.analysis.desugar - -class DesugarPhase { - -} diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/analysis/desugar/NestedPatternMatch.scala b/engine/runtime/src/main/scala/org/enso/interpreter/analysis/desugar/NestedPatternMatch.scala deleted file mode 100644 index 3c7b72b9976..00000000000 --- a/engine/runtime/src/main/scala/org/enso/interpreter/analysis/desugar/NestedPatternMatch.scala +++ /dev/null @@ -1,5 +0,0 @@ -package org.enso.interpreter.analysis.desugar - -class NestedPatternMatch { - -} diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/analysis/desugar/UnderscoreToLambda.scala b/engine/runtime/src/main/scala/org/enso/interpreter/analysis/desugar/UnderscoreToLambda.scala deleted file mode 100644 index 274ed104d20..00000000000 --- a/engine/runtime/src/main/scala/org/enso/interpreter/analysis/desugar/UnderscoreToLambda.scala +++ /dev/null @@ -1,5 +0,0 @@ -package org.enso.interpreter.analysis.desugar - -class UnderscoreToLambda { - -} diff --git a/project/plugins.sbt b/project/plugins.sbt index c9c05549dfc..c5543250133 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1 +1,2 @@ -addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10") \ No newline at end of file +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10") +addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.3.4") \ No newline at end of file