diff options
24 files changed, 263 insertions, 49 deletions
diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 847177e2f..79e97becb 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -906,13 +906,6 @@ class Definitions { /** The type of the boxed class corresponding to primitive value type `tp`. */ def boxedType(tp: Type)(implicit ctx: Context): TypeRef = boxedTypes(scalaClassName(tp)) - def wrapArrayMethodName(elemtp: Type): TermName = { - val cls = elemtp.classSymbol - if (cls.isPrimitiveValueClass) nme.wrapXArray(cls.name) - else if (cls.derivesFrom(ObjectClass) && !cls.isPhantomClass) nme.wrapRefArray - else nme.genericWrapArray - } - type PrimitiveClassEnc = Int val ByteEnc = 2 diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index c14108d2e..b27bff37a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1607,6 +1607,7 @@ object Parsers { * LocalModifier ::= abstract | final | sealed | implicit | lazy */ def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = { + @tailrec def loop(mods: Modifiers): Modifiers = { if (allowed contains in.token) { val isAccessMod = accessModifierTokens contains in.token @@ -2057,7 +2058,7 @@ object Parsers { val name = ident().toTypeName val constr = atPos(in.lastOffset) { val tparams = typeParamClauseOpt(ParamOwner.Class) - val cmods = constrModsOpt() + val cmods = constrModsOpt(name) val vparamss = paramClauses(name, mods is Case) makeConstructor(tparams, vparamss).withMods(cmods) @@ -2070,11 +2071,11 @@ object Parsers { /** ConstrMods ::= AccessModifier * | Annotation {Annotation} (AccessModifier | `this') */ - def constrModsOpt(): Modifiers = { + def constrModsOpt(owner: Name): Modifiers = { val mods = modifiers(accessModifierTokens, annotsAsMods()) if (mods.hasAnnotations && !mods.hasFlags) if (in.token == THIS) in.nextToken() - else syntaxError("`private', `protected', or `this' expected") + else syntaxError(AnnotatedPrimaryConstructorRequiresModifierOrThis(owner), mods.annotations.last.pos) mods } diff --git a/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 86f34e64d..e20f846ac 100644 --- a/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -6,6 +6,7 @@ import parsing.Tokens._ import scala.annotation.switch import scala.collection.mutable.StringBuilder import core.Contexts.Context +import util.Chars.{ LF, FF, CR, SU } import Highlighting.{Highlight, HighlightBuffer} /** This object provides functions for syntax highlighting in the REPL */ @@ -26,7 +27,7 @@ object SyntaxHighlighting { private def valDef(str: String) = ValDefColor + str + NoColor private def operator(str: String) = TypeColor + str + NoColor private def annotation(str: String) = - if (str.trim == "@") str else AnnotationColor + str + NoColor + if (str.trim == "@") str else { AnnotationColor + str + NoColor } private val tripleQs = Console.RED_B + "???" + NoColor private val keywords: Seq[String] = for { @@ -152,7 +153,11 @@ object SyntaxHighlighting { var open = 1 while (open > 0 && remaining.nonEmpty) { curr = takeChar() - newBuf += curr + if (curr == '@') { + appendWhile('@', !typeEnders.contains(_), annotation) + newBuf append CommentColor + } + else newBuf += curr if (curr == '*' && remaining.nonEmpty) { curr = takeChar() @@ -163,6 +168,11 @@ object SyntaxHighlighting { newBuf += curr if (curr == '*') open += 1 } + + (curr: @switch) match { + case LF | FF | CR | SU => newBuf append CommentColor + case _ => () + } } prev = curr newBuf append NoColor @@ -236,6 +246,11 @@ object SyntaxHighlighting { newBuf += curr closing = 0 } + + (curr: @switch) match { + case LF | FF | CR | SU => newBuf append LiteralColor + case _ => () + } } newBuf append NoColor prev = curr diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index 190445d60..17eb8d39b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -5,9 +5,12 @@ package reporting import core.Contexts.Context import core.Decorators._ import printing.Highlighting.{Blue, Red} +import printing.SyntaxHighlighting import diagnostic.{ErrorMessageID, Message, MessageContainer, NoExplanation} import diagnostic.messages._ import util.SourcePosition +import util.Chars.{ LF, CR, FF, SU } +import scala.annotation.switch import scala.collection.mutable @@ -38,20 +41,37 @@ trait MessageRendering { */ def sourceLines(pos: SourcePosition)(implicit ctx: Context): (List[String], List[String], Int) = { var maxLen = Int.MinValue - def render(xs: List[Int]) = - xs.map(pos.source.offsetToLine(_)) - .map { lineNbr => - val prefix = s"${lineNbr + 1} |" - maxLen = math.max(maxLen, prefix.length) - (prefix, pos.lineContent(lineNbr).stripLineEnd) - } - .map { case (prefix, line) => - val lnum = Red(" " * math.max(0, maxLen - prefix.length) + prefix) - hl"$lnum$line" - } + def render(offsetAndLine: (Int, String)): String = { + val (offset, line) = offsetAndLine + val lineNbr = pos.source.offsetToLine(offset) + val prefix = s"${lineNbr + 1} |" + maxLen = math.max(maxLen, prefix.length) + val lnum = Red(" " * math.max(0, maxLen - prefix.length) + prefix).show + lnum + line.stripLineEnd + } + + def linesFrom(arr: Array[Char]): List[String] = { + def pred(c: Char) = (c: @switch) match { + case LF | CR | FF | SU => true + case _ => false + } + val (line, rest0) = arr.span(!pred(_)) + val (_, rest) = rest0.span(pred) + new String(line) :: { if (rest.isEmpty) Nil else linesFrom(rest) } + } + val syntax = + if (ctx.settings.color.value != "never") + SyntaxHighlighting(pos.linesSlice).toArray + else pos.linesSlice + val lines = linesFrom(syntax) val (before, after) = pos.beforeAndAfterPoint - (render(before), render(after), maxLen) + + ( + before.zip(lines).map(render), + after.zip(lines.drop(before.length)).map(render), + maxLen + ) } /** The column markers aligned under the error */ diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index 2bf15cb7c..bbd93eb30 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -51,6 +51,7 @@ public enum ErrorMessageID { ExpectedTokenButFoundID, MixedLeftAndRightAssociativeOpsID, CantInstantiateAbstractClassOrTraitID, + AnnotatedPrimaryConstructorRequiresModifierOrThisID, ; public int errorNumber() { diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 34190c114..e367e9ab2 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1146,4 +1146,18 @@ object messages { |""".stripMargin } + case class AnnotatedPrimaryConstructorRequiresModifierOrThis(cls: Name)(implicit ctx: Context) + extends Message(AnnotatedPrimaryConstructorRequiresModifierOrThisID) { + val kind = "Syntax" + val msg = hl"""${"private"}, ${"protected"}, or ${"this"} expected for annotated primary constructor""" + val explanation = + hl"""|When using annotations with a primary constructor of a class, + |the annotation must be followed by an access modifier + |(${"private"} or ${"protected"}) or ${"this"}. + | + |For example: + | ${"class Sample @deprecated this(param: Parameter) { ..."} + | ^^^^ + |""".stripMargin + } } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 870198027..222717e7e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -450,7 +450,16 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def typedArg(arg: Arg, formal: Type): Arg = arg def addArg(arg: TypedArg, formal: Type) = - ok = ok & isCompatible(argType(arg, formal), formal) + ok = ok & { + argType(arg, formal) match { + case ref: TermRef if ref.denot.isOverloaded => + // in this case we could not resolve overloading because no alternative + // matches expected type + false + case argtpe => + isCompatible(argtpe, formal) + } + } def makeVarArg(n: Int, elemFormal: Type) = {} def fail(msg: => Message, arg: Arg) = ok = false diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 48f9243f7..1ca7eb107 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -327,7 +327,7 @@ object Checking { if (!sym.is(Deferred)) fail(i"`@native' members may not have implementation") } - else if (sym.is(Deferred, butNot = Param) && !sym.isSelfSym) { + else if (sym.is(Deferred, butNot = Param) && !sym.isType && !sym.isSelfSym) { if (!sym.owner.isClass || sym.owner.is(Module) || sym.owner.isAnonymousClass) fail(i"only classes can have declared but undefined members$varNote") checkWithDeferred(Private) diff --git a/compiler/src/dotty/tools/dotc/util/SourcePosition.scala b/compiler/src/dotty/tools/dotc/util/SourcePosition.scala index aad4995d8..be5c46995 100644 --- a/compiler/src/dotty/tools/dotc/util/SourcePosition.scala +++ b/compiler/src/dotty/tools/dotc/util/SourcePosition.scala @@ -12,9 +12,14 @@ extends interfaces.SourcePosition { def lineContent: String = source.lineContent(point) def point: Int = pos.point + /** The line of the position, starting at 0 */ def line: Int = source.offsetToLine(point) + /** Extracts the lines from the underlying source file as `Array[Char]`*/ + def linesSlice: Array[Char] = + source.content.slice(source.startOfLine(start), source.nextLine(end)) + /** The lines of the position */ def lines: List[Int] = List.range(source.offsetToLine(start), source.offsetToLine(end + 1)) match { @@ -25,9 +30,6 @@ extends interfaces.SourcePosition { def lineOffsets: List[Int] = lines.map(source.lineToOffset(_)) - def lineContent(lineNumber: Int): String = - source.lineContent(source.lineToOffset(lineNumber)) - def beforeAndAfterPoint: (List[Int], List[Int]) = lineOffsets.partition(_ <= point) diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 971a40a1b..d6e687a1e 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -198,4 +198,18 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertTrue("expected trait", isTrait) } + @Test def constructorModifier = + checkMessagesAfter("frontend") { + """ + |class AnotherClass @deprecated () + """.stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + val defn = ictx.definitions + + assertMessageCount(1, messages) + val AnnotatedPrimaryConstructorRequiresModifierOrThis(cls) :: Nil = messages + assertEquals("AnotherClass", cls.show) + } } diff --git a/doc-tool/resources/_layouts/api-page.html b/doc-tool/resources/_layouts/api-page.html index 878ec8d8f..2d2f0265a 100644 --- a/doc-tool/resources/_layouts/api-page.html +++ b/doc-tool/resources/_layouts/api-page.html @@ -134,12 +134,25 @@ extraCSS: {% endfor %} {% endif %} - {% if member.kind == "type" and member.alias != null %} + {% if member.kind == "type" %} + {% for tparam in member.typeParams %} + {% if forloop.first %} + <span class="no-left">[</span> + {% endif %} + {% if forloop.last %} + <span class="no-left">{{ tparam }}</span> + <span class="no-left">]</span> + {% else %} + <span class="no-left">{{ tparam }}, </span> + {% endif %} + {% endfor %} + {% if member.alias != null %} <span class="type-alias"> <span class="equals"> = </span> {% renderRef member.alias %} </span> {% endif %} + {% endif %} {% if member.returnValue %} <span class="no-left">: {% renderRef member.returnValue %}</span> diff --git a/doc-tool/resources/css/dottydoc.css b/doc-tool/resources/css/dottydoc.css index 7e7c501bf..0b833830c 100644 --- a/doc-tool/resources/css/dottydoc.css +++ b/doc-tool/resources/css/dottydoc.css @@ -328,3 +328,9 @@ pre > code.hljs { padding: 10px; background: transparent; } + +blockquote { + padding: 0 1em; + color: #777; + border-left: 0.25em solid #ddd; +} diff --git a/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala index 7f44c5656..cfb66fa56 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala @@ -8,7 +8,7 @@ import dotc.CompilationUnit import dotc.config.Printers.dottydoc import dotc.core.Contexts.Context import dotc.core.Comments.ContextDocstrings -import dotc.core.Types.NoType +import dotc.core.Types.{PolyType, NoType} import dotc.core.Phases.Phase import dotc.core.Symbols.{ Symbol, NoSymbol } @@ -92,8 +92,16 @@ class DocASTPhase extends Phase { val sym = t.symbol if (sym.is(Flags.Synthetic | Flags.Param)) NonEntity - else - TypeAliasImpl(sym, annotations(sym), flags(t), t.name.show.split("\\$\\$").last, path(sym), alias(t.rhs.tpe)) + else { + val tparams = t.rhs.tpe match { + case tp: PolyType => tp.paramRefs.zip(tp.variances).map { case (tp, variance) => + val varianceSym = if (variance == 1) "+" else if (variance == -1) "-" else "" + varianceSym + tp.paramName.show + } + case _ => Nil + } + TypeAliasImpl(sym, annotations(sym), flags(t), t.name.show.split("\\$\\$").last, path(sym), alias(t.rhs.tpe), tparams) + } /** trait */ case t @ TypeDef(n, rhs) if t.symbol.is(Flags.Trait) => diff --git a/doc-tool/src/dotty/tools/dottydoc/core/transform.scala b/doc-tool/src/dotty/tools/dottydoc/core/transform.scala index e4b9ae6b6..5174c0922 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/transform.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/transform.scala @@ -92,6 +92,7 @@ object transform { t.name, t.path, t.alias, + t.typeParams, t.comment, t.parent ) diff --git a/doc-tool/src/dotty/tools/dottydoc/model/JavaConverters.scala b/doc-tool/src/dotty/tools/dottydoc/model/JavaConverters.scala index 239656141..4a9bfce0c 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/JavaConverters.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/JavaConverters.scala @@ -189,6 +189,7 @@ object JavaConverters { "name" -> ent.name, "path" -> ent.path.asJava, "alias" -> ent.alias.map(_.asJava).asJava, + "typeParams" -> ent.typeParams.asJava, "comment" -> ent.comment.map(_.asJava).asJava, "hasShortenedDocstring" -> ent.hasShortenedDocstring, "isPrivate" -> ent.isPrivate, diff --git a/doc-tool/src/dotty/tools/dottydoc/model/comment/CommentParser.scala b/doc-tool/src/dotty/tools/dottydoc/model/comment/CommentParser.scala index b7a33a7ef..5ff28f914 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/comment/CommentParser.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/comment/CommentParser.scala @@ -6,6 +6,7 @@ import dotty.tools.dottydoc.util.syntax._ import dotty.tools.dotc.util.Positions._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Decorators._ import scala.collection.mutable import dotty.tools.dotc.config.Printers.dottydoc import scala.util.matching.Regex @@ -196,7 +197,7 @@ trait CommentParser extends util.MemberLookup { ) for ((key, _) <- bodyTags) ctx.docbase.warn( - s"Tag '@${key.name}' is not recognised", + hl"Tag '${"@" + key.name}' is not recognised", // FIXME: here the position is stretched out over the entire comment, // with the point being at the very end. This ensures that the entire // comment will be visible in error reporting. A more fine-grained diff --git a/doc-tool/src/dotty/tools/dottydoc/model/entities.scala b/doc-tool/src/dotty/tools/dottydoc/model/entities.scala index d35077816..d0f1a82c7 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/entities.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/entities.scala @@ -108,7 +108,7 @@ trait Package extends Entity with Members with SuperTypes { val kind = "package" } -trait TypeAlias extends Entity with Modifiers { +trait TypeAlias extends Entity with Modifiers with TypeParams { val kind = "type" def alias: Option[Reference] def isAbstract: Boolean = !alias.isDefined diff --git a/doc-tool/src/dotty/tools/dottydoc/model/factories.scala b/doc-tool/src/dotty/tools/dottydoc/model/factories.scala index 568b532b7..3e766a990 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/factories.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/factories.scala @@ -4,7 +4,8 @@ package model import comment._ import references._ import dotty.tools.dotc -import dotc.core.Types._ +import dotc.core.Types +import Types._ import dotc.core.TypeApplications._ import dotc.core.Contexts.Context import dotc.core.Symbols.{ Symbol, ClassSymbol } @@ -105,17 +106,8 @@ object factories { case ci: ClassInfo => typeRef(ci.cls.name.show, query = ci.typeSymbol.showFullName) - case tl: PolyType => { - // FIXME: should be handled correctly - // example, in `Option`: - // - // ```scala - // def companion: GenericCompanion[collection.Iterable] - // ``` - // - // Becomes: def companion: [+X0] -> collection.Iterable[X0] - typeRef(tl.show + " (not handled)") - } + case tl: PolyType => + expandTpe(tl.resType) case OrType(left, right) => OrTypeReference(expandTpe(left), expandTpe(right)) @@ -148,6 +140,8 @@ object factories { prefix + tp.name.show.split("\\$").last } .toList + case tp: Types.TypeAlias => + typeParams(tp.alias.typeSymbol) case _ => Nil } diff --git a/doc-tool/src/dotty/tools/dottydoc/model/internal.scala b/doc-tool/src/dotty/tools/dottydoc/model/internal.scala index bf50c0232..b50c93ee5 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/internal.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/internal.scala @@ -31,6 +31,7 @@ object internal { name: String, path: List[String], alias: Option[Reference], + typeParams: List[String] = Nil, var comment: Option[Comment] = None, var parent: Entity = NonEntity ) extends TypeAlias diff --git a/doc-tool/src/dotty/tools/dottydoc/staticsite/tags.scala b/doc-tool/src/dotty/tools/dottydoc/staticsite/tags.scala index cd2ea587d..bc4a74c4f 100644 --- a/doc-tool/src/dotty/tools/dottydoc/staticsite/tags.scala +++ b/doc-tool/src/dotty/tools/dottydoc/staticsite/tags.scala @@ -180,6 +180,7 @@ object tags { override def render(ctx: TemplateContext, nodes: LNode*): AnyRef = (nodes(0).render(ctx), nodes(1).render(ctx)) match { case (t: Title, parent: String) => renderTitle(t, parent) + case (t: Title, _) => renderTitle(t, "./") // file is in top dir case _ => null } } diff --git a/tests/neg/customArgs/typers.scala b/tests/neg/customArgs/typers.scala index 49742ebbd..9432ab039 100644 --- a/tests/neg/customArgs/typers.scala +++ b/tests/neg/customArgs/typers.scala @@ -30,7 +30,7 @@ object typers { } type L[X] = scala.collection.immutable.List[X] - type M[X, Y] <: scala.collection.immutable.Map[X, Y] // error: only classes can have declared but undefined members + type M[X, Y] <: scala.collection.immutable.Map[X, Y] // old-error: only classes can have declared but undefined members object hk { def f(x: L) // error: missing type parameter diff --git a/tests/neg/i1050c.scala b/tests/neg/i1050c.scala index 19570eb83..ecfaa3ea3 100644 --- a/tests/neg/i1050c.scala +++ b/tests/neg/i1050c.scala @@ -27,7 +27,8 @@ object Tiark4 { v.brand("boom!") } object V { // error: cannot be instantiated - type Y >: Any <: Nothing // error: only classes can have declared but undefined members + type Y >: Any <: Nothing // old-error: only classes can have declared but undefined members + type Z } object Tiark5 { trait A { type L <: Nothing } diff --git a/tests/neg/i2033.scala b/tests/neg/i2033.scala new file mode 100644 index 000000000..b28a0d99e --- /dev/null +++ b/tests/neg/i2033.scala @@ -0,0 +1,21 @@ +import java.io._ +import collection._ +object Test { + def check(obj: AnyRef): Unit = { + val bos = new ByteArrayOutputStream() + val out = new ObjectOutputStream(println) // error + val arr = bos toByteArray () + val in = (()) + val deser = () + val lhs = mutable LinkedHashSet () + check(lhs) + } +} + +// minimization +object Test2 { + class ObjectOutputStream(out: String) { + def this() = this("") + } + val out = new ObjectOutputStream(println) // error +} diff --git a/tests/run/hmap.scala b/tests/run/hmap.scala new file mode 100644 index 000000000..d84419ce1 --- /dev/null +++ b/tests/run/hmap.scala @@ -0,0 +1,97 @@ +trait Tuple +case class TCons[H, T <: Tuple](h: H, t: T) extends Tuple +final case object TNil extends Tuple + +// Type level natural numbers ------------------------------------------------- + +sealed trait Nat +sealed trait Succ[P <: Nat] extends Nat +sealed trait Zero extends Nat + +// Accessor type class to compute the N'th element of an Tuple L -------------- + +trait At[L <: Tuple, N <: Nat, Out] { + def apply(l: L): Out +} + +object At { + implicit def caseZero[H, T <: Tuple]: At[H TCons T, Zero, H] = + new At[H TCons T, Zero, H] { + def apply(l: H TCons T): H = { + val (h TCons _) = l + h + } + } + + implicit def caseN[H, T <: Tuple, N <: Nat, O] + (implicit a: At[T, N, O]): At[H TCons T, Succ[N], O] = + new At[H TCons T, Succ[N], O] { + def apply(l: H TCons T): O = { + val (_ TCons t) = l + a(t) + } + } +} + +// An HMap is an Tuple with HEntry elements. We are reusing Tuple for it's nice syntax + +final case class HEntry[K, V](value: V) + +// Accessor type class to compute the element of type K in a HMap L ----------- + +trait PhantomGet[K, M <: Tuple, I <: Nat] // extends PhantomAny + +object PhantomGet { + implicit def getHead[K, V, T <: Tuple] + : PhantomGet[K, HEntry[K, V] TCons T, Zero] = null + + implicit def getTail[K, H, T <: Tuple, I <: Nat] + (implicit t: PhantomGet[K, T, I]) + : PhantomGet[K, H TCons T, Succ[I]] = null +} + +// Syntax --------------------------------------------------------------------- + +object syntax { + object hmap { + implicit class HmapGet[M <: Tuple](m: M) { + def get[K, V, I <: Nat](k: K) + (implicit + g: PhantomGet[k.type, M, I], + a: At[M, I, HEntry[k.type, V]] + ): V = a(m).value + } + + def --[K, V](key: K, value: V) = HEntry[key.type, V](value) + } +} + +object Test { + def main(args: Array[String]): Unit = { + import syntax.hmap._ + + val map1 = + TCons(HEntry[K = "name"]("foo"), + TCons(HEntry[K = "genre"](true), + TCons(HEntry[K = "moneyz"](123), + TCons(HEntry[K = "cat"]("bar"), + (TNil: TNil.type))))) + + assert(map1.get("name") == "foo") + assert(map1.get("genre") == true) + assert(map1.get("moneyz") == 123) + assert(map1.get("cat") == "bar") + + val map2 = + TCons(--("name" , "foo"), + TCons(--("genre" , true), + TCons(--("moneyz", 123), + TCons(--("cat" , "bar"), + TNil)))) + + assert(map2.get("name") == "foo") + assert(map2.get("genre") == true) + assert(map2.get("moneyz") == 123) + assert(map2.get("cat") == "bar") + } +} |