From 7b66796606c054edfb7dd574acb63241998895d2 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Wed, 4 Dec 2024 15:55:15 +0800 Subject: [PATCH 01/16] Experimental support for annotations --- .../main/scala/hkmc2/codegen/Lowering.scala | 4 ++ .../scala/hkmc2/semantics/BlockImpl.scala | 4 +- .../scala/hkmc2/semantics/Elaborator.scala | 10 +++ .../src/main/scala/hkmc2/semantics/Term.scala | 4 ++ .../src/main/scala/hkmc2/syntax/Parser.scala | 55 ++++++++------ .../src/main/scala/hkmc2/syntax/Tree.scala | 8 +++ .../src/test/mlscript/syntax/Annotations.mls | 71 +++++++++++++++++++ 7 files changed, 134 insertions(+), 22 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/syntax/Annotations.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index f3ed24a25..dc0f0e4b2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -296,6 +296,10 @@ class Lowering(using TL, Raise, Elaborator.State): term(finallyDo)(_ => End()), k(Value.Ref(l)) ) + + case Annotated(prefix, receiver) => + // TODO: handle annotations + term(receiver)(k) case Error => End("error") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala index 1577dfa2d..6a11d435c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala @@ -3,7 +3,7 @@ package semantics import mlscript.utils.*, shorthands.* import syntax.Tree.* -import hkmc2.syntax.TypeOrTermDef +import hkmc2.syntax.{Annotations, TypeOrTermDef} trait BlockImpl(using Elaborator.State): @@ -14,7 +14,7 @@ trait BlockImpl(using Elaborator.State): val definedSymbols: Array[Str -> BlockMemberSymbol] = desugStmts .flatMap: - case td: syntax.TypeOrTermDef => + case Annotations(_, td: syntax.TypeOrTermDef) => td.name match case L(_) => Nil case R(id) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index d612056e4..0b9fc3d6a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -429,6 +429,13 @@ extends Importer: case Spread(kw, kwLoc, body) => raise(ErrorReport(msg"Illegal position for '${kw.name}' spread operator." -> tree.toLoc :: Nil)) Term.Error + case Annotated(prefix, receiver) => + val ann = prefix match + case App(_: Ident | _: SynthSel | _: Sel, _) | _: Ident | _: SynthSel | _: Sel => term(prefix) + case _ => + raise(ErrorReport(msg"Unsupported annotation prefix." -> prefix.toLoc :: Nil)) + Term.Error + Term.Annotated(ann, term(receiver)) // case _ => // ??? @@ -750,6 +757,9 @@ extends Importer: case Modified(Keyword.`declare`, absLoc, body) :: sts => // TODO: pass declare to `go` go(body :: sts, acc) + case Annotated(prefix, receiver) :: sts => + // TODO: pass annotations to `go` + go(receiver :: sts, acc) case (result: Tree) :: Nil => val res = term(result) (Term.Blk(acc.reverse, res), ctx) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 5a67992e4..8e9678fc4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -39,6 +39,7 @@ enum Term extends Statement: case Throw(result: Term) case Try(body: Term, finallyDo: Term) case Handle(lhs: LocalSymbol, rhs: Term, defs: ObjBody) + case Annotated(prefix: Term, receiver: Term) lazy val symbol: Opt[Symbol] = this match case Ref(sym) => S(sym) @@ -72,6 +73,7 @@ enum Term extends Statement: case RegRef(reg, value) => "reference creation" case Assgn(lhs, rhs) => "assignment" case Deref(ref) => "dereference" + case Annotated(prefix, receiver) => "annotation" end Term import Term.* @@ -126,6 +128,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: case Try(body, finallyDo) => body :: finallyDo :: Nil case Handle(lhs, rhs, defs) => rhs :: defs._1 :: Nil case Neg(e) => e :: Nil + case Annotated(prefix, receiver) => prefix :: receiver :: Nil protected def children: Ls[Located] = this match case t: Lit => t.lit.asTree :: Nil @@ -196,6 +199,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: cls.tparams.map(_.showDbg).mkStringOr(", ", "[", "]")}${ cls.paramsOpt.fold("")(_.toString)} ${cls.body}" case Import(sym, file) => s"import ${sym} from ${file}" + case Annotated(prefix, receiver) => s"@${prefix.showDbg} ${receiver.showDbg}" final case class LetDecl(sym: LocalSymbol) extends Statement diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index f9b88ec9a..7ad91fa40 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -101,6 +101,11 @@ object Parser: loc.origin.fph.getLineColAt(loc.spanStart) match case (ln, _, col) => s"Ln $ln Col $col" + extension (trees: Ls[Tree]) + /** Note that the innermost annotation is the leftmost. */ + def annotate(tree: Tree): Tree = trees.foldLeft(tree): + case (acc, ann) => Annotated(ann, acc) + end Parser import Parser._ @@ -232,21 +237,24 @@ abstract class Parser( maybeIndented((p, i) => p.block(allowNewlines = i)) - def block(allowNewlines: Bool)(using Line): Ls[Tree] = blockOf(prefixRules, allowNewlines) + def block(allowNewlines: Bool)(using Line): Ls[Tree] = blockOf(prefixRules, Nil, allowNewlines) - def blockOf(rule: ParseRule[Tree], allowNewlines: Bool)(using Line): Ls[Tree] = - wrap(rule.name)(blockOfImpl(rule, allowNewlines)) - def blockOfImpl(rule: ParseRule[Tree], allowNewlines: Bool): Ls[Tree] = - def blockContOf(rule: ParseRule[Tree]): Ls[Tree] = + def blockOf(rule: ParseRule[Tree], headAnnotations: Ls[Tree], allowNewlines: Bool)(using Line): Ls[Tree] = + wrap(rule.name)(blockOfImpl(rule, headAnnotations, allowNewlines)) + def blockOfImpl(rule: ParseRule[Tree], headAnnotations: Ls[Tree], allowNewlines: Bool): Ls[Tree] = + def blockContOf(rule: ParseRule[Tree], headAnnotations: Ls[Tree] = Nil): Ls[Tree] = yeetSpaces match - case (COMMA, _) :: _ => consume; blockOf(rule, allowNewlines) - case (SEMI, _) :: _ => consume; blockOf(rule, allowNewlines) - case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, allowNewlines) + case (COMMA, _) :: _ => consume; blockOf(rule, headAnnotations, allowNewlines) + case (SEMI, _) :: _ => consume; blockOf(rule, headAnnotations, allowNewlines) + case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, headAnnotations, allowNewlines) case _ => Nil cur match case Nil => Nil - case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, allowNewlines) - case (SPACE, _) :: _ => consume; blockOf(rule, allowNewlines) + case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, headAnnotations, allowNewlines) + case (SPACE, _) :: _ => consume; blockOf(rule, headAnnotations, allowNewlines) + case (IDENT("@", _), l0) :: _ => + consume + blockOf(rule, simpleExpr(AppPrec) :: headAnnotations, allowNewlines) case (tok @ (id: IDENT), loc) :: _ => Keyword.all.get(id.name) match case S(kw) => @@ -256,14 +264,14 @@ abstract class Parser( yeetSpaces match case (tok @ BRACKETS(Indent | Curly, toks), loc) :: _ if subRule.blkAlt.isEmpty => consume - val blk = rec(toks, S(tok.innerLoc), tok.describe).concludeWith(_.blockOf(subRule, allowNewlines)) // FIXME allowNewlines? + val blk = rec(toks, S(tok.innerLoc), tok.describe).concludeWith(_.blockOf(subRule, Nil, allowNewlines)) // FIXME allowNewlines? if blk.isEmpty then err((msg"Expected ${subRule.whatComesAfter} ${subRule.mkAfterStr}; found end of block instead" -> S(loc) :: Nil)) errExpr - blk ::: blockContOf(rule) + blk ::: blockContOf(rule) // TODO: apply headAnnotations case _ => val res = parseRule(CommaPrecNext, subRule).getOrElse(errExpr) - exprCont(res, CommaPrecNext, false) :: blockContOf(rule) + headAnnotations.annotate(exprCont(res, CommaPrecNext, false)) :: blockContOf(rule) case N => // TODO dedup this common-looking logic: @@ -273,10 +281,14 @@ abstract class Parser( yeetSpaces match case (tok @ BRACKETS(Indent | Curly, toks), loc) :: _ /* if subRule.blkAlt.isEmpty */ => consume + headAnnotations match + case Nil => () + case head :: _ => + err((msg"Blocks are not allowed after annotations" -> head.toLoc :: Nil)) prefixRules.kwAlts.get(kw.name) match case S(subRule) if subRule.blkAlt.isEmpty => rec(toks, S(tok.innerLoc), tok.describe).concludeWith { p => - p.blockOf(subRule.map(e => parseRule(CommaPrecNext, exprAlt.rest).map(res => exprAlt.k(e, res)).getOrElse(errExpr)), allowNewlines) + p.blockOf(subRule.map(e => parseRule(CommaPrecNext, exprAlt.rest).map(res => exprAlt.k(e, res)).getOrElse(errExpr)), Nil, allowNewlines) } ++ blockContOf(rule) case _ => TODO(cur) @@ -284,15 +296,14 @@ abstract class Parser( prefixRules.kwAlts.get(kw.name) match case S(subRule) => val e = parseRule(CommaPrecNext, subRule).getOrElse(errExpr) - parseRule(CommaPrecNext, exprAlt.rest).map(res => exprAlt.k(e, res)).getOrElse(errExpr) :: blockContOf(rule) + headAnnotations.annotate(parseRule(CommaPrecNext, exprAlt.rest).map(res => exprAlt.k(e, res)).getOrElse(errExpr)) :: blockContOf(rule) case N => // TODO dedup? err((msg"Expected ${rule.whatComesAfter} ${rule.mkAfterStr}; found ${tok.describe} instead" -> S(loc) :: Nil)) - errExpr :: blockContOf(rule) + headAnnotations.annotate(errExpr) :: blockContOf(rule) case N => err((msg"Expected ${rule.whatComesAfter} ${rule.mkAfterStr}; found ${tok.describe} instead" -> S(loc) :: Nil)) - errExpr :: blockContOf(rule) - + headAnnotations.annotate(errExpr) :: blockContOf(rule) case N => val lhs = tryParseExp(CommaPrecNext, tok, loc, rule).getOrElse(errExpr) cur match @@ -301,9 +312,9 @@ abstract class Parser( val rhs = expr(CommaPrecNext) Def(lhs, rhs) :: blockContOf(rule) case _ => - lhs :: blockContOf(rule) + headAnnotations.annotate(lhs) :: blockContOf(rule) case (tok, loc) :: _ => - tryParseExp(CommaPrecNext, tok, loc, rule).getOrElse(errExpr) :: blockContOf(rule) + headAnnotations.annotate(tryParseExp(CommaPrecNext, tok, loc, rule).getOrElse(errExpr)) :: blockContOf(rule) private def tryParseExp[A](prec: Int, tok: Token, loc: Loc, rule: ParseRule[A]): Opt[A] = @@ -467,6 +478,10 @@ abstract class Parser( def simpleExpr(prec: Int)(using Line): Tree = wrap(prec)(simpleExprImpl(prec)) def simpleExprImpl(prec: Int): Tree = yeetSpaces match + case (IDENT("@", _), l0) :: _ => + consume + val ann = simpleExpr(AppPrec) + Annotated(ann, simpleExpr(prec)) case (IDENT(nme, sym), loc) :: _ => Keyword.all.get(nme) match case S(kw) => // * Expressions starting with keywords should be handled in parseRule diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 8cc4ca76a..4c568710b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -74,6 +74,7 @@ enum Tree extends AutoLocated: case RegRef(reg: Tree, value: Tree) case Effectful(eff: Tree, body: Tree) case Spread(kw: Keyword.Ellipsis, kwLoc: Opt[Loc], body: Opt[Tree]) + case Annotated(prefix: Tree, receiver: Tree) def children: Ls[Tree] = this match case _: Empty | _: Error | _: Ident | _: Literal => Nil @@ -107,6 +108,7 @@ enum Tree extends AutoLocated: case Open(bod) => bod :: Nil case Def(lhs, rhs) => lhs :: rhs :: Nil case Spread(_, _, body) => body.toList + case Annotated(prefix, receiver) => prefix :: receiver :: Nil def describe: Str = this match case Empty() => "empty" @@ -142,6 +144,7 @@ enum Tree extends AutoLocated: case Handle(_, _, _, _) => "handle" case Def(lhs, rhs) => "defining assignment" case Spread(_, _, _) => "spread" + case Annotated(prefix, receiver) => "annotated" def showDbg: Str = toString // TODO @@ -191,6 +194,11 @@ object Apps: def unapply(t: Tree): S[(Tree, Ls[Tup])] = t match case App(Apps(id, args), arg: Tup) => S(id, args :+ arg) case t => S(t, Nil) + +object Annotations: + def unapply(t: Tree): Opt[(Ls[Tree], Tree)] = t match + case Annotated(p, Annotations(ps, recv)) => S(p :: ps, recv) + case other => S((Nil, other)) sealed abstract class OuterKind(val desc: Str) diff --git a/hkmc2/shared/src/test/mlscript/syntax/Annotations.mls b/hkmc2/shared/src/test/mlscript/syntax/Annotations.mls new file mode 100644 index 000000000..1c198e46d --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/syntax/Annotations.mls @@ -0,0 +1,71 @@ +:js + +module tailrec + +tailrec +//│ = tailrec { class: [class tailrec] } + +@tailrec fun fact_n(n, acc) = + if n == 0 then acc else fact(n - 1, n * acc) + +fun fact(n) = + @tailrec fun go(n, acc) = + if n == 0 then acc else go(n - 1, n * acc) + go(n, 1) + +module compile + +class SomePattern + +:e // TODO: pattern compilation +fun foo(x) = if x is @compile SomePattern then "yes" else "no" +//│ ╔══[ERROR] Unrecognized pattern. +//│ ║ l.21: fun foo(x) = if x is @compile SomePattern then "yes" else "no" +//│ ╙── ^^^^^^^^^^^^^^^^^^^ + +module debug + +@debug 0 +//│ = 0 + +@debug 1 + 2 +//│ = 3 + +(@debug 1 + 2) +//│ = 3 + +(@debug 1) + 2 +//│ = 3 + +(1 + @debug 2) +//│ = 3 + +class Log(msg: Str) + +fact(@Log 5) +//│ = 120 + +class Freezed(degree: Num) + +@Freezed(-273.15) class AbsoluteZero + +@Freezed(-18) class Beverage(name: Str) + +@Freezed(-4) let drink = Beverage("Coke") +//│ drink = Beverage { name: 'Coke' } + +module Foo with + class Bar(qax: Str) + +@Foo.Bar("baz") class Qux + +@42 class Qux + +:pe +@1 + 2 class Qux +//│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead +//│ ║ l.65: @1 + 2 class Qux +//│ ╙── ^^^^^ +//│ = 2 + +@(1 + 2) class Qux From a2a97bb684c2d308d2d2a41d6955c9eb17ce0af5 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Tue, 7 Jan 2025 21:32:49 +0800 Subject: [PATCH 02/16] Should apply annotations to continued declarations --- .../shared/src/main/scala/hkmc2/syntax/Parser.scala | 8 +++----- .../shared/src/test/mlscript/syntax/Annotations.mls | 13 +++++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index e4cfed5b2..cb477e3e5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -268,7 +268,7 @@ abstract class Parser( if blk.isEmpty then err((msg"Expected ${subRule.whatComesAfter} ${subRule.mkAfterStr}; found end of block instead" -> S(loc) :: Nil)) errExpr - blk ::: blockContOf(rule) // TODO: apply headAnnotations + blk.map(headAnnotations.annotate) ::: blockContOf(rule) // TODO: apply headAnnotations case _ => val res = parseRule(CommaPrecNext, subRule).getOrElse(errExpr) headAnnotations.annotate(exprCont(res, CommaPrecNext, false)) :: blockContOf(rule) @@ -281,10 +281,8 @@ abstract class Parser( yeetSpaces match case (tok @ BRACKETS(Indent | Curly, toks), loc) :: _ /* if subRule.blkAlt.isEmpty */ => consume - headAnnotations match - case Nil => () - case head :: _ => - err((msg"Blocks are not allowed after annotations" -> head.toLoc :: Nil)) + if headAnnotations.nonEmpty then + err((msg"Blocks cannot be annotated" -> S(loc) :: Nil)) prefixRules.kwAlts.get(kw.name) match case S(subRule) if subRule.blkAlt.isEmpty => rec(toks, S(tok.innerLoc), tok.describe).concludeWith { p => diff --git a/hkmc2/shared/src/test/mlscript/syntax/Annotations.mls b/hkmc2/shared/src/test/mlscript/syntax/Annotations.mls index 1c198e46d..f2fe42f0f 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/Annotations.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/Annotations.mls @@ -69,3 +69,16 @@ module Foo with //│ = 2 @(1 + 2) class Qux + +module inline + +@inline +fun + min(x, y) = if x < y then x else y + max(x, y) = if x > y then x else y + +@inline let + abs(x) = if x < 0 then -x else x + clamp(x, lo, hi) = min(max(x, lo), hi) +//│ abs = [Function (anonymous)] +//│ clamp = [Function (anonymous)] From fc2a6092a5aa246424bca58752b94e666ad36cfb Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Tue, 7 Jan 2025 22:35:32 +0800 Subject: [PATCH 03/16] No longer ignore annotations in the elaborator --- .../src/main/scala/hkmc2/bbml/bbML.scala | 10 +- .../main/scala/hkmc2/codegen/Lowering.scala | 4 +- .../scala/hkmc2/semantics/Elaborator.scala | 96 ++++++++++++------- .../src/main/scala/hkmc2/semantics/Term.scala | 34 ++++--- .../hkmc2/semantics/ucs/Translator.scala | 2 +- .../src/test/mlscript/parser/Handler.mls | 6 +- .../Declarations.mls} | 39 -------- .../mlscript/syntax/annotations/Pattern.mls | 9 ++ .../mlscript/syntax/annotations/Useless.mls | 74 ++++++++++++++ .../mlscript/ucs/papers/OperatorSplit.mls | 1 + .../mlscript/ucs/syntax/NestedOpSplits.mls | 1 + 11 files changed, 177 insertions(+), 99 deletions(-) rename hkmc2/shared/src/test/mlscript/syntax/{Annotations.mls => annotations/Declarations.mls} (54%) create mode 100644 hkmc2/shared/src/test/mlscript/syntax/annotations/Pattern.mls create mode 100644 hkmc2/shared/src/test/mlscript/syntax/annotations/Useless.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala b/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala index 40d6701ff..c1b71266e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala @@ -221,7 +221,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL, scope: Scope): val cr = freshVar(new TempSymbol(S(unq), "ctx")) constrain(tryMkMono(ty, body), BbCtx.codeTy(tv, cr)) (tv, cr, eff) - case blk @ Term.Blk(LetDecl(sym) :: DefineVar(sym2, rhs) :: Nil, body) if sym2 is sym => // TODO: more than one!! + case blk @ Term.Blk(LetDecl(sym, _) :: DefineVar(sym2, rhs) :: Nil, body) if sym2 is sym => // TODO: more than one!! val (rhsTy, rhsCtx, rhsEff) = typeCode(rhs)(using ctx) val nestCtx = ctx.nextLevel given BbCtx = nestCtx @@ -414,19 +414,19 @@ class BBTyper(using elState: Elaborator.State, tl: TL, scope: Scope): case (term: Term) :: stats => effBuff += typeCheck(term)._2 goStats(stats) - case LetDecl(sym) :: DefineVar(sym2, rhs) :: stats => + case LetDecl(sym, _) :: DefineVar(sym2, rhs) :: stats => require(sym2 is sym) val (rhsTy, eff) = typeCheck(rhs) effBuff += eff ctx += sym -> rhsTy goStats(stats) - case TermDefinition(_, Fun, sym, ps :: Nil, sig, Some(body), _, _) :: stats => + case TermDefinition(_, Fun, sym, ps :: Nil, sig, Some(body), _, _, _) :: stats => typeFunDef(sym, Term.Lam(ps, body), sig, ctx) goStats(stats) - case TermDefinition(_, Fun, sym, Nil, sig, Some(body), _, _) :: stats => + case TermDefinition(_, Fun, sym, Nil, sig, Some(body), _, _, _) :: stats => typeFunDef(sym, body, sig, ctx) // * may be a case expressions goStats(stats) - case TermDefinition(_, Fun, sym, _, S(sig), None, _, _) :: stats => + case TermDefinition(_, Fun, sym, _, S(sig), None, _, _, _) :: stats => ctx += sym -> typeType(sig) goStats(stats) case (clsDef: ClassDef) :: stats => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index f8991e9f4..a98f7eb44 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -125,7 +125,7 @@ class Lowering(using TL, Raise, Elaborator.State): case td: TermDefinition if td.k is syntax.Fun => L(td) case s => R(s) val (privateFlds, rest2) = rest1.partitionMap: - case LetDecl(sym: TermSymbol) => L(sym) + case LetDecl(sym: TermSymbol, _) => L(sym) case s => R(s) val publicFlds = rest2.collect: case td @ TermDefinition(k = (_: syntax.Val)) => td @@ -146,7 +146,7 @@ class Lowering(using TL, Raise, Elaborator.State): case _ => // TODO handle term(st.Blk(stats, res))(k) - case st.Blk((LetDecl(sym)) :: stats, res) => + case st.Blk((LetDecl(sym, _)) :: stats, res) => term(st.Blk(stats, res))(k) case st.Blk((DefineVar(sym, rhs)) :: stats, res) => subTerm(rhs): r => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 52f5248e7..f438cbde8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -150,8 +150,8 @@ class Elaborator(val tl: TraceLogger, val wd: os.Path) extends Importer: import tl.* - def mkLetBinding(sym: LocalSymbol, rhs: Term): Ls[Statement] = - LetDecl(sym) :: DefineVar(sym, rhs) :: Nil + def mkLetBinding(sym: LocalSymbol, rhs: Term, annotations: Ls[Term]): Ls[Statement] = + LetDecl(sym, annotations) :: DefineVar(sym, rhs) :: Nil def resolveField(srcTree: Tree, base: Opt[Symbol], nme: Ident): Opt[FieldSymbol] = base match @@ -205,7 +205,7 @@ extends Importer: val lt = term(lhs) val sym = TempSymbol(S(lt), "old") Term.Blk( - LetDecl(sym) :: DefineVar(sym, lt) :: Nil, Term.Try(Term.Blk( + LetDecl(sym, Nil) :: DefineVar(sym, lt) :: Nil, Term.Try(Term.Blk( Term.Assgn(lt, term(rhs)) :: Nil, term(bod), ), Term.Assgn(lt, sym.ref(id)))) @@ -468,10 +468,13 @@ extends Importer: raise(ErrorReport(msg"Illegal position for '_' placeholder." -> tree.toLoc :: Nil)) Term.Error case Annotated(prefix, receiver) => + raise(WarningReport( + msg"The annotation is not used" -> prefix.toLoc :: + msg"Because annotations cannot be applied to ${receiver.describe}" -> receiver.toLoc :: Nil)) val ann = prefix match case App(_: Ident | _: SynthSel | _: Sel, _) | _: Ident | _: SynthSel | _: Sel => term(prefix) case _ => - raise(ErrorReport(msg"Unsupported annotation prefix." -> prefix.toLoc :: Nil)) + raise(ErrorReport(msg"Unsupported annotation expression." -> prefix.toLoc :: Nil)) Term.Error Term.Annotated(ann, term(receiver)) // case _ => @@ -529,11 +532,25 @@ extends Importer: // TODO extract this into a separate method @tailrec - def go(sts: Ls[Tree], acc: Ls[Statement]): Ctxl[(Term.Blk, Ctx)] = sts match + def go(sts: Ls[Tree], annotations: Ls[Term], acc: Ls[Statement]): Ctxl[(Term.Blk, Ctx)] = + def reportUnusedAnnotations: Unit = + if annotations.nonEmpty then + raise(WarningReport( + msg"The annotation is not used" -> (annotations.foldLeft[Opt[Loc]](N): + case (acc, ann) => acc match + case N => ann.toLoc + case S(loc) => S(loc ++ ann.toLoc)) :: + (sts.headOption match + case N => msg"Because nothing follows" -> blk.toLoc.map(_.right) + case S(head) => msg"Because annotations cannot be applied to ${head.describe}" -> head.toLoc + ) :: Nil)) + sts match case Nil => + reportUnusedAnnotations val res = unit (Term.Blk(acc.reverse, res), ctx) case Open(bod) :: sts => + reportUnusedAnnotations bod match case Jux(bse, Block(sts)) => some(bse -> some(sts)) @@ -544,7 +561,7 @@ extends Importer: raise(ErrorReport(msg"Illegal 'open' statement shape." -> bod.toLoc :: Nil)) N match - case N => go(sts, acc) + case N => go(sts, annotations, acc) case S((base, importedTrees)) => base match case baseId: Ident => @@ -568,14 +585,15 @@ extends Importer: raise(ErrorReport(msg"Illegal 'open' statement element." -> t.toLoc :: Nil)) Nil (ctx elem_++ importedNames).givenIn: - go(sts, acc) + go(sts, Nil, acc) case N => raise(ErrorReport(msg"Name not found: ${baseId.name}" -> baseId.toLoc :: Nil)) - go(sts, acc) + go(sts, Nil, acc) case _ => raise(ErrorReport(msg"Illegal 'open' statement base." -> base.toLoc :: Nil)) - go(sts, acc) + go(sts, Nil, acc) case (m @ Modified(Keyword.`import`, absLoc, arg)) :: sts => + reportUnusedAnnotations val (newCtx, newAcc) = arg match case Tree.StrLit(path) => val stmt = importPath(path) @@ -587,7 +605,7 @@ extends Importer: arg.toLoc :: Nil)) (ctx, acc) newCtx.givenIn: - go(sts, newAcc) + go(sts, Nil, newAcc) case (hd @ LetLike(`let`, Apps(id: Ident, tups), rhso, N)) :: sts if id.name.headOption.exists(_.isLower) => val sym = @@ -597,17 +615,18 @@ extends Importer: case S(rhs) => val rrhs = tups.foldRight(rhs): Tree.InfixApp(_, Keyword.`=>`, _) - mkLetBinding(sym, term(rrhs)) reverse_::: acc + mkLetBinding(sym, term(rrhs), annotations) reverse_::: acc case N => if tups.nonEmpty then raise(ErrorReport(msg"Expected a right-hand side for let bindings with parameters" -> hd.toLoc :: Nil)) - LetDecl(sym) :: acc + LetDecl(sym, annotations) :: acc (ctx + (id.name -> sym)) givenIn: - go(sts, newAcc) + go(sts, Nil, newAcc) case (tree @ LetLike(`let`, lhs, S(rhs), N)) :: sts => raise(ErrorReport(msg"Unsupported let binding shape" -> tree.toLoc :: Nil)) - go(sts, Term.Error :: acc) + go(sts, Nil, Term.Error :: acc) case (hd @ Handle(id: Ident, cls: Ident, Block(sts_), N)) :: sts => + reportUnusedAnnotations val sym = fieldOrVarSym(HandlerBind, id) log(s"Processing `handle` statement $id (${sym}) ${ctx.outer}") @@ -618,10 +637,10 @@ extends Importer: case trm => raise(WarningReport(msg"Terms in handler block do nothing" -> trm.toLoc :: Nil)) val tds = elabed.stats.map { - case td @ TermDefinition(owner, Fun, sym, params, sign, body, resSym, flags) => + case td @ TermDefinition(owner, Fun, sym, params, sign, body, resSym, flags, annotations) => params.reverse match case ParamList(_, value :: Nil, _) :: newParams => - val newTd = TermDefinition(owner, Fun, sym, newParams.reverse, sign, body, resSym, flags) + val newTd = TermDefinition(owner, Fun, sym, newParams.reverse, sign, body, resSym, flags, annotations) S(HandlerTermDefinition(value.sym, newTd)) case _ => raise(ErrorReport(msg"Handler function is missing resumption parameter" -> td.toLoc :: Nil)) @@ -634,29 +653,30 @@ extends Importer: val newAcc = Term.Handle(sym, term(cls), tds) :: acc ctx + (id.name -> sym) givenIn: - go(sts, newAcc) + go(sts, Nil, newAcc) case (tree @ Handle(_, _, _, N)) :: sts => raise(ErrorReport(msg"Unsupported handle binding shape" -> tree.toLoc :: Nil)) - go(sts, Term.Error :: acc) + go(sts, Nil, Term.Error :: acc) case Def(lhs, rhs) :: sts => + reportUnusedAnnotations lhs match case id: Ident => val r = term(rhs) ctx.get(id.name) match case S(elem) => elem.symbol match - case S(sym: LocalSymbol) => go(sts, DefineVar(sym, r) :: acc) + case S(sym: LocalSymbol) => go(sts, Nil, DefineVar(sym, r) :: acc) case N => // TODO lookup in members? inherited/refined stuff? raise(ErrorReport(msg"Name not found: ${id.name}" -> id.toLoc :: Nil)) - go(sts, Term.Error :: acc) + go(sts, Nil, Term.Error :: acc) case App(base, args) => - go(Def(base, InfixApp(args, Keyword.`=>`, rhs)) :: sts, acc) + go(Def(base, InfixApp(args, Keyword.`=>`, rhs)) :: sts, Nil, acc) case _ => raise(ErrorReport(msg"Unrecognized definitional assignment left-hand side: ${lhs.describe}" -> lhs.toLoc :: Nil)) // TODO BE - go(sts, Term.Error :: acc) + go(sts, Nil, Term.Error :: acc) case (td @ TermDef(k, nme, rhs)) :: sts => log(s"Processing term definition $nme") td.name match @@ -681,7 +701,7 @@ extends Importer: val b = rhs.map(term(_)(using newCtx)) val r = FlowSymbol(s"‹result of ${sym}›") val tdf = TermDefinition(owner, k, sym, pss, s, b, r, - TermDefFlags.empty.copy(isModMember = isModMember)) + TermDefFlags.empty.copy(isModMember = isModMember), annotations) sym.defn = S(tdf) // indicates if the function really returns a module @@ -712,10 +732,11 @@ extends Importer: case _ => () tdf - go(sts, tdf :: acc) + go(sts, Nil, tdf :: acc) case L(d) => + reportUnusedAnnotations raise(d) - go(sts, acc) + go(sts, Nil, acc) case (td @ TypeDef(k, head, extension, body)) :: sts => assert((k is Als) || (k is Cls) || (k is Mod) || (k is Obj) || (k is Pat), k) val nme = td.name match @@ -766,7 +787,7 @@ extends Importer: assert(body.isEmpty) val d = given Ctx = newCtx - semantics.TypeDef(alsSym, tps, extension.map(term(_)), N) + semantics.TypeDef(alsSym, tps, extension.map(term(_)), N, annotations) alsSym.defn = S(d) d case Pat => @@ -777,7 +798,7 @@ extends Importer: log(s"pattern body is ${td.extension}") val translate = new ucs.Translator(this) val bod = translate(ps.map(_.params).getOrElse(Nil), td.extension.getOrElse(die)) - val pd = PatternDef(owner, patSym, tps, ps, ObjBody(Term.Blk(bod, Term.Lit(UnitLit(true))))) + val pd = PatternDef(owner, patSym, tps, ps, ObjBody(Term.Blk(bod, Term.Lit(UnitLit(true)))), annotations) patSym.defn = S(pd) pd case k: (Mod.type | Obj.type) => @@ -791,7 +812,7 @@ extends Importer: // case S(t) => block(t :: Nil) case S(t) => ??? case N => (new Term.Blk(Nil, Term.Lit(UnitLit(true))), ctx) - ModuleDef(owner, clsSym, tps, ps, k, ObjBody(bod)) + ModuleDef(owner, clsSym, tps, ps, k, ObjBody(bod), annotations) clsSym.defn = S(cd) cd case Cls => @@ -805,31 +826,34 @@ extends Importer: // case S(t) => block(t :: Nil) case S(t) => ??? case N => (new Term.Blk(Nil, Term.Lit(UnitLit(true))), ctx) - ClassDef(owner, Cls, clsSym, tps, ps, ObjBody(bod)) + ClassDef(owner, Cls, clsSym, tps, ps, ObjBody(bod), annotations) clsSym.defn = S(cd) cd - go(sts, defn :: acc) + go(sts, Nil, defn :: acc) case Modified(Keyword.`abstract`, absLoc, body) :: sts => ??? // TODO: pass abstract to `go` - go(body :: sts, acc) + go(body :: sts, annotations, acc) case Modified(Keyword.`declare`, absLoc, body) :: sts => // TODO: pass declare to `go` - go(body :: sts, acc) + go(body :: sts, annotations, acc) case Annotated(prefix, receiver) :: sts => // TODO: pass annotations to `go` - go(receiver :: sts, acc) + val newAnnotations = annotations :+ term(prefix) + go(receiver :: sts, newAnnotations, acc) case (result: Tree) :: Nil => + reportUnusedAnnotations val res = term(result) (Term.Blk(acc.reverse, res), ctx) case (st: Tree) :: sts => + reportUnusedAnnotations val res = term(st) // TODO reject plain term statements? Currently, `(1, 2)` is allowed to elaborate (tho it should be rejected in type checking later) - go(sts, res :: acc) + go(sts, Nil, res :: acc) end go c.withMembers(members, c.outer).givenIn: - go(blk.desugStmts, Nil) + go(blk.desugStmts, Nil, Nil) def fieldOrVarSym(k: TermDefKind, id: Ident)(using Ctx): LocalSymbol & NamedSymbol = @@ -912,7 +936,7 @@ extends Importer: def computeVariances(s: Statement): Unit = val trav = VarianceTraverser() def go(s: Statement): Unit = s match - case TermDefinition(_, k, sym, pss, sign, body, r, _) => + case TermDefinition(_, k, sym, pss, sign, body, r, _, _) => pss.foreach(ps => ps.params.foreach(trav.traverseType(S(false)))) sign.foreach(trav.traverseType(S(true))) body match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 2736e5cd6..ca0c80113 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -111,15 +111,15 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: case Forall(_, body) => body :: Nil case WildcardTy(in, out) => in.toList ++ out.toList case CompType(lhs, rhs, _) => lhs :: rhs :: Nil - case LetDecl(sym) => Nil + case LetDecl(sym, annotations) => annotations.flatMap(_.subTerms) case DefineVar(sym, rhs) => rhs :: Nil case Region(_, body) => body :: Nil case RegRef(reg, value) => reg :: value :: Nil case Assgn(lhs, rhs) => lhs :: rhs :: Nil case SetRef(lhs, rhs) => lhs :: rhs :: Nil case Deref(term) => term :: Nil - case TermDefinition(_, k, _, ps, sign, body, res, _) => - ps.toList.flatMap(_.subTerms) ::: sign.toList ::: body.toList + case TermDefinition(_, k, _, ps, sign, body, res, _, annotations) => + ps.toList.flatMap(_.subTerms) ::: sign.toList ::: body.toList ::: annotations.flatMap(_.subTerms) case cls: ClassDef => cls.paramsOpt.toList.flatMap(_.subTerms) ::: cls.body.blk :: Nil case mod: ModuleDef => @@ -181,7 +181,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: case New(cls, args) => s"new ${cls.toString}(${args.mkString(", ")})" case SelProj(pre, cls, proj) => s"${pre.showDbg}.${cls.showDbg}#${proj.name}" case Asc(term, ty) => s"${term.toString}: ${ty.toString}" - case LetDecl(sym) => s"let ${sym}" + case LetDecl(sym, _) => s"let ${sym}" case DefineVar(sym, rhs) => s"${sym} = ${rhs.showDbg}" case Handle(lhs, rhs, defs) => s"handle ${lhs} = ${rhs} ${defs}" case Region(name, body) => s"region ${name.nme} in ${body.showDbg}" @@ -192,7 +192,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: case CompType(lhs, rhs, pol) => s"${lhs.showDbg} ${if pol then "|" else "&"} ${rhs.showDbg}" case Error => "" case Tup(fields) => fields.map(_.showDbg).mkString("[", ", ", "]") - case TermDefinition(_, k, sym, ps, sign, body, res, flags) => s"${flags} ${k.str} ${sym}${ + case TermDefinition(_, k, sym, ps, sign, body, res, flags, _) => s"${flags} ${k.str} ${sym}${ ps.map(_.showDbg).mkString("") }${sign.fold("")(": "+_.showDbg)}${ body match @@ -206,7 +206,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: case Import(sym, file) => s"import ${sym} from ${file}" case Annotated(prefix, receiver) => s"@${prefix.showDbg} ${receiver.showDbg}" -final case class LetDecl(sym: LocalSymbol) extends Statement +final case class LetDecl(sym: LocalSymbol, annotations: Ls[Term]) extends Statement final case class DefineVar(sym: LocalSymbol, rhs: Term) extends Statement @@ -228,6 +228,7 @@ final case class TermDefinition( body: Opt[Term], resSym: FlowSymbol, flags: TermDefFlags, + annotations: Ls[Term], ) extends Companion final case class HandlerTermDefinition( @@ -261,6 +262,7 @@ sealed abstract class ClassLikeDef extends TypeLikeDef: val tparams: Ls[TyParam] val kind: ClsLikeKind val body: ObjBody + val annotations: Ls[Term] case class ModuleDef( @@ -270,6 +272,7 @@ case class ModuleDef( paramsOpt: Opt[ParamList], kind: ClsLikeKind, body: ObjBody, + annotations: Ls[Term], ) extends ClassLikeDef with Companion case class PatternDef( @@ -277,7 +280,8 @@ case class PatternDef( sym: PatternSymbol, tparams: Ls[TyParam], paramsOpt: Opt[ParamList], - body: ObjBody + body: ObjBody, + annotations: Ls[Term], ) extends ClassLikeDef: self => val kind: ClsLikeKind = Pat @@ -290,14 +294,15 @@ sealed abstract class ClassDef extends ClassLikeDef: val paramsOpt: Opt[ParamList] val body: ObjBody val companion: Opt[Companion] + val annotations: Ls[Term] object ClassDef: - def apply(owner: Opt[InnerSymbol], kind: ClsLikeKind, sym: InnerSymbol, tparams: Ls[TyParam], paramsOpt: Opt[ParamList], body: ObjBody): ClassDef = + def apply(owner: Opt[InnerSymbol], kind: ClsLikeKind, sym: InnerSymbol, tparams: Ls[TyParam], paramsOpt: Opt[ParamList], body: ObjBody, annotations: Ls[Term]): ClassDef = paramsOpt match case S(params) => Parameterized(owner, kind, sym.asInstanceOf// TODO: improve - , tparams, params, body, N) + , tparams, params, body, N, annotations) case N => Plain(owner, kind, sym.asInstanceOf// TODO: improve - , tparams, body, N) + , tparams, body, N, annotations) def unapply(cls: ClassDef): Opt[(ClassSymbol, Ls[TyParam], Opt[ParamList], ObjBody)] = S((cls.sym, cls.tparams, cls.paramsOpt, cls.body)) @@ -306,7 +311,8 @@ object ClassDef: owner: Opt[InnerSymbol], kind: ClsLikeKind, sym: ClassSymbol, tparams: Ls[TyParam], params: ParamList, - body: ObjBody, companion: Opt[ModuleDef] + body: ObjBody, companion: Opt[ModuleDef], + annotations: Ls[Term] ) extends ClassDef: val paramsOpt: Opt[ParamList] = S(params) @@ -314,7 +320,8 @@ object ClassDef: owner: Opt[InnerSymbol], kind: ClsLikeKind, sym: ClassSymbol, tparams: Ls[TyParam], - body: ObjBody, companion: Opt[Companion] + body: ObjBody, companion: Opt[Companion], + annotations: Ls[Term] ) extends ClassDef: val paramsOpt: Opt[ParamList] = N @@ -325,7 +332,8 @@ case class TypeDef( sym: TypeAliasSymbol, tparams: Ls[TyParam], rhs: Opt[Term], - companion: Opt[Companion] + companion: Opt[Companion], + annotations: Ls[Term], ) extends TypeLikeDef diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala index ab8fe1ed0..3d729bd87 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala @@ -212,7 +212,7 @@ class Translator(val elaborator: Elaborator) val ps = PlainParamList(Param(FldFlags.empty, scrut, N) :: Nil) val body = Term.IfLike(Keyword.`if`, topmost)(normalize(topmost)) val res = FlowSymbol(s"result of $name") - TermDefinition(N, Fun, sym, ps :: Nil, N, S(body), res, TermDefFlags.empty) + TermDefinition(N, Fun, sym, ps :: Nil, N, S(body), res, TermDefFlags.empty, Nil) /** Translate a list of extractor/matching functions for the given pattern. * There are currently two functions: `unapply` and `unapplyStringPrefix`. diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index a1a69d515..119282e5f 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -52,7 +52,7 @@ handle h = Eff with fun f()(r) = r(0) in foo(h) -//│ Elab: { { handle globalThis:block#6.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#6),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#6.h#666) } } +//│ Elab: { { handle globalThis:block#6.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#6),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#6.h#666) } } :e ( @@ -73,7 +73,7 @@ handle h = Eff with fun f()(r) = r(0) fun g(a)()()(r) = r(1) foo(h) -//│ Elab: { handle globalThis:block#8.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#8.h#666) } +//│ Elab: { handle globalThis:block#8.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#8.h#666) } :e handle h = Eff with @@ -124,4 +124,4 @@ foo(h) //│ ╔══[WARNING] Terms in handler block do nothing //│ ║ l.122: 12345 //│ ╙── ^^^^^ -//│ Elab: { handle globalThis:block#11.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#11),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#11),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#11.h#666) } +//│ Elab: { handle globalThis:block#11.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#11),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#11),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#11.h#666) } diff --git a/hkmc2/shared/src/test/mlscript/syntax/Annotations.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls similarity index 54% rename from hkmc2/shared/src/test/mlscript/syntax/Annotations.mls rename to hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls index f2fe42f0f..b6d1fe5c6 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/Annotations.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls @@ -13,38 +13,6 @@ fun fact(n) = if n == 0 then acc else go(n - 1, n * acc) go(n, 1) -module compile - -class SomePattern - -:e // TODO: pattern compilation -fun foo(x) = if x is @compile SomePattern then "yes" else "no" -//│ ╔══[ERROR] Unrecognized pattern. -//│ ║ l.21: fun foo(x) = if x is @compile SomePattern then "yes" else "no" -//│ ╙── ^^^^^^^^^^^^^^^^^^^ - -module debug - -@debug 0 -//│ = 0 - -@debug 1 + 2 -//│ = 3 - -(@debug 1 + 2) -//│ = 3 - -(@debug 1) + 2 -//│ = 3 - -(1 + @debug 2) -//│ = 3 - -class Log(msg: Str) - -fact(@Log 5) -//│ = 120 - class Freezed(degree: Num) @Freezed(-273.15) class AbsoluteZero @@ -61,13 +29,6 @@ module Foo with @42 class Qux -:pe -@1 + 2 class Qux -//│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead -//│ ║ l.65: @1 + 2 class Qux -//│ ╙── ^^^^^ -//│ = 2 - @(1 + 2) class Qux module inline diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Pattern.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Pattern.mls new file mode 100644 index 000000000..cba666633 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Pattern.mls @@ -0,0 +1,9 @@ +module compile + +class SomePattern + +:e // TODO: pattern compilation +fun foo(x) = if x is @compile SomePattern then "yes" else "no" +//│ ╔══[ERROR] Unrecognized pattern. +//│ ║ l.6: fun foo(x) = if x is @compile SomePattern then "yes" else "no" +//│ ╙── ^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Useless.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Useless.mls new file mode 100644 index 000000000..d69f26fb0 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Useless.mls @@ -0,0 +1,74 @@ +module debug + +:w +@debug 0 +//│ ╔══[WARNING] The annotation is not used +//│ ║ l.4: @debug 0 +//│ ║ ^^^^^ +//│ ╟── Because annotations cannot be applied to integer literal +//│ ║ l.4: @debug 0 +//│ ╙── ^ + +:w +@debug 1 + 2 +//│ ╔══[WARNING] The annotation is not used +//│ ║ l.13: @debug 1 + 2 +//│ ║ ^^^^^ +//│ ╟── Because annotations cannot be applied to application +//│ ║ l.13: @debug 1 + 2 +//│ ╙── ^^^^^ + +:w +(@debug 1 + 2) +//│ ╔══[WARNING] The annotation is not used +//│ ║ l.22: (@debug 1 + 2) +//│ ║ ^^^^^ +//│ ╟── Because annotations cannot be applied to application +//│ ║ l.22: (@debug 1 + 2) +//│ ╙── ^^^^^ + +:w +(@debug 1) + 2 +//│ ╔══[WARNING] The annotation is not used +//│ ║ l.31: (@debug 1) + 2 +//│ ║ ^^^^^ +//│ ╟── Because annotations cannot be applied to integer literal +//│ ║ l.31: (@debug 1) + 2 +//│ ╙── ^ + +:w +(1 + @debug 2) +//│ ╔══[WARNING] The annotation is not used +//│ ║ l.40: (1 + @debug 2) +//│ ║ ^^^^^ +//│ ╟── Because annotations cannot be applied to integer literal +//│ ║ l.40: (1 + @debug 2) +//│ ╙── ^ + +class Log(msg: Str) + +:w +fact(@Log 5) +//│ FAILURE: Unexpected type error +//│ ╔══[ERROR] Name not found: fact +//│ ║ l.51: fact(@Log 5) +//│ ╙── ^^^^ +//│ ╔══[WARNING] The annotation is not used +//│ ║ l.51: fact(@Log 5) +//│ ║ ^^^ +//│ ╟── Because annotations cannot be applied to integer literal +//│ ║ l.51: fact(@Log 5) +//│ ╙── ^ + +:pe +@1 + 2 class Qux +//│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead +//│ ║ l.64: @1 + 2 class Qux +//│ ╙── ^^^^^ +//│ FAILURE: Unexpected warning +//│ ╔══[WARNING] The annotation is not used +//│ ║ l.64: @1 + 2 class Qux +//│ ║ ^ +//│ ╟── Because annotations cannot be applied to application +//│ ║ l.64: @1 + 2 class Qux +//│ ╙── ^^^ diff --git a/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls b/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls index f483fe174..3cfc65dc9 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls @@ -188,6 +188,7 @@ fun example(args) = //│ tail = Else of Lit of StrLit of "medium" //│ resSym = ‹result of member:example› //│ flags = () +//│ annotations = Nil //│ res = Lit of UnitLit of true diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls index 3a148b53a..02afbd776 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls @@ -60,6 +60,7 @@ fun f(x) = //│ tail = Else of Lit of IntLit of 1 //│ resSym = ‹result of member:f› //│ flags = () +//│ annotations = Nil //│ res = Lit of UnitLit of true From 01b88d38000690c3e2d522afa54115b4d9acdf19 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Wed, 8 Jan 2025 16:24:18 +0800 Subject: [PATCH 04/16] Fix tests --- .../syntax/annotations/Declarations.mls | 14 +++++++++--- .../mlscript/syntax/annotations/Useless.mls | 22 ++++++++----------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls index b6d1fe5c6..e5288d9df 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls @@ -6,8 +6,8 @@ tailrec //│ = tailrec { class: [class tailrec] } @tailrec fun fact_n(n, acc) = - if n == 0 then acc else fact(n - 1, n * acc) - + if n == 0 then acc else fact_n(n - 1, n * acc) + fun fact(n) = @tailrec fun go(n, acc) = if n == 0 then acc else go(n - 1, n * acc) @@ -33,13 +33,21 @@ module Foo with module inline +// All functions are annotated with @inline. @inline fun min(x, y) = if x < y then x else y max(x, y) = if x > y then x else y - + @inline let abs(x) = if x < 0 then -x else x clamp(x, lo, hi) = min(max(x, lo), hi) //│ abs = [Function (anonymous)] //│ clamp = [Function (anonymous)] + +// Only the first variable is annotated with @inline. +let + @inline success = 0 + failure = 1 +//│ failure = 1 +//│ success = 0 diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Useless.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Useless.mls index d69f26fb0..e8762eb96 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Useless.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Useless.mls @@ -48,27 +48,23 @@ module debug class Log(msg: Str) :w -fact(@Log 5) -//│ FAILURE: Unexpected type error -//│ ╔══[ERROR] Name not found: fact -//│ ║ l.51: fact(@Log 5) -//│ ╙── ^^^^ +id(@Log 5) //│ ╔══[WARNING] The annotation is not used -//│ ║ l.51: fact(@Log 5) -//│ ║ ^^^ +//│ ║ l.51: id(@Log 5) +//│ ║ ^^^ //│ ╟── Because annotations cannot be applied to integer literal -//│ ║ l.51: fact(@Log 5) -//│ ╙── ^ +//│ ║ l.51: id(@Log 5) +//│ ╙── ^ :pe +:w @1 + 2 class Qux //│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead -//│ ║ l.64: @1 + 2 class Qux +//│ ║ l.61: @1 + 2 class Qux //│ ╙── ^^^^^ -//│ FAILURE: Unexpected warning //│ ╔══[WARNING] The annotation is not used -//│ ║ l.64: @1 + 2 class Qux +//│ ║ l.61: @1 + 2 class Qux //│ ║ ^ //│ ╟── Because annotations cannot be applied to application -//│ ║ l.64: @1 + 2 class Qux +//│ ║ l.61: @1 + 2 class Qux //│ ╙── ^^^ From 069a64bdec5942c88931c7d5e4e1ca43dbecf078 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Wed, 8 Jan 2025 17:08:18 +0800 Subject: [PATCH 05/16] Clean up code and tests --- .../scala/hkmc2/semantics/Elaborator.scala | 84 +++++++++---------- .../src/main/scala/hkmc2/semantics/Term.scala | 8 +- .../src/main/scala/hkmc2/syntax/Parser.scala | 42 +++++----- .../src/main/scala/hkmc2/syntax/Tree.scala | 8 +- .../syntax/annotations/Declarations.mls | 25 ++++++ .../syntax/annotations/Unsupported.mls | 72 ++++++++++++++++ .../mlscript/syntax/annotations/Useless.mls | 70 ---------------- 7 files changed, 167 insertions(+), 142 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls delete mode 100644 hkmc2/shared/src/test/mlscript/syntax/annotations/Useless.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index f438cbde8..1b548f67b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -467,16 +467,16 @@ extends Importer: case Under() => raise(ErrorReport(msg"Illegal position for '_' placeholder." -> tree.toLoc :: Nil)) Term.Error - case Annotated(prefix, receiver) => + case Annotated(lhs, rhs) => raise(WarningReport( - msg"The annotation is not used" -> prefix.toLoc :: - msg"Because annotations cannot be applied to ${receiver.describe}" -> receiver.toLoc :: Nil)) - val ann = prefix match - case App(_: Ident | _: SynthSel | _: Sel, _) | _: Ident | _: SynthSel | _: Sel => term(prefix) - case _ => - raise(ErrorReport(msg"Unsupported annotation expression." -> prefix.toLoc :: Nil)) - Term.Error - Term.Annotated(ann, term(receiver)) + msg"This annotation qualifier is not applied" -> lhs.toLoc :: + msg"Because annotations are not supported for ${rhs.describe}" -> rhs.toLoc :: Nil)) + val qualifier = lhs match + case App(_: (Ident | SynthSel | Sel), _) | _: (Ident | SynthSel | Sel) => term(lhs) + case _ => + raise(ErrorReport(msg"Illegal annotation qualifier shape." -> lhs.toLoc :: Nil)) + Term.Error + Term.Annotated(qualifier, term(rhs)) // case _ => // ??? @@ -532,25 +532,25 @@ extends Importer: // TODO extract this into a separate method @tailrec - def go(sts: Ls[Tree], annotations: Ls[Term], acc: Ls[Statement]): Ctxl[(Term.Blk, Ctx)] = - def reportUnusedAnnotations: Unit = - if annotations.nonEmpty then - raise(WarningReport( - msg"The annotation is not used" -> (annotations.foldLeft[Opt[Loc]](N): - case (acc, ann) => acc match - case N => ann.toLoc - case S(loc) => S(loc ++ ann.toLoc)) :: - (sts.headOption match - case N => msg"Because nothing follows" -> blk.toLoc.map(_.right) - case S(head) => msg"Because annotations cannot be applied to ${head.describe}" -> head.toLoc - ) :: Nil)) + def go(sts: Ls[Tree], qualifiers: Ls[Term], acc: Ls[Statement]): Ctxl[(Term.Blk, Ctx)] = + /** Call this function when the following term cannot be annotated. */ + def reportUnusedQualifiers: Unit = if qualifiers.nonEmpty then raise: + WarningReport: + msg"The annotation qualifier is not applied" -> (qualifiers.foldLeft[Opt[Loc]](N): + case (acc, ann) => acc match + case N => ann.toLoc + case S(loc) => S(loc ++ ann.toLoc) + ) :: (sts.headOption match + case N => msg"Because the target term is missing" -> blk.toLoc.map(_.right) + case S(head) => msg"Because annotations are not supported for ${head.describe}" -> head.toLoc + ) :: Nil sts match case Nil => - reportUnusedAnnotations + reportUnusedQualifiers val res = unit (Term.Blk(acc.reverse, res), ctx) case Open(bod) :: sts => - reportUnusedAnnotations + reportUnusedQualifiers bod match case Jux(bse, Block(sts)) => some(bse -> some(sts)) @@ -561,7 +561,7 @@ extends Importer: raise(ErrorReport(msg"Illegal 'open' statement shape." -> bod.toLoc :: Nil)) N match - case N => go(sts, annotations, acc) + case N => go(sts, qualifiers, acc) case S((base, importedTrees)) => base match case baseId: Ident => @@ -593,7 +593,7 @@ extends Importer: raise(ErrorReport(msg"Illegal 'open' statement base." -> base.toLoc :: Nil)) go(sts, Nil, acc) case (m @ Modified(Keyword.`import`, absLoc, arg)) :: sts => - reportUnusedAnnotations + reportUnusedQualifiers val (newCtx, newAcc) = arg match case Tree.StrLit(path) => val stmt = importPath(path) @@ -615,18 +615,18 @@ extends Importer: case S(rhs) => val rrhs = tups.foldRight(rhs): Tree.InfixApp(_, Keyword.`=>`, _) - mkLetBinding(sym, term(rrhs), annotations) reverse_::: acc + mkLetBinding(sym, term(rrhs), qualifiers) reverse_::: acc case N => if tups.nonEmpty then raise(ErrorReport(msg"Expected a right-hand side for let bindings with parameters" -> hd.toLoc :: Nil)) - LetDecl(sym, annotations) :: acc + LetDecl(sym, qualifiers) :: acc (ctx + (id.name -> sym)) givenIn: go(sts, Nil, newAcc) case (tree @ LetLike(`let`, lhs, S(rhs), N)) :: sts => raise(ErrorReport(msg"Unsupported let binding shape" -> tree.toLoc :: Nil)) go(sts, Nil, Term.Error :: acc) case (hd @ Handle(id: Ident, cls: Ident, Block(sts_), N)) :: sts => - reportUnusedAnnotations + reportUnusedQualifiers val sym = fieldOrVarSym(HandlerBind, id) log(s"Processing `handle` statement $id (${sym}) ${ctx.outer}") @@ -659,7 +659,7 @@ extends Importer: go(sts, Nil, Term.Error :: acc) case Def(lhs, rhs) :: sts => - reportUnusedAnnotations + reportUnusedQualifiers lhs match case id: Ident => val r = term(rhs) @@ -701,7 +701,7 @@ extends Importer: val b = rhs.map(term(_)(using newCtx)) val r = FlowSymbol(s"‹result of ${sym}›") val tdf = TermDefinition(owner, k, sym, pss, s, b, r, - TermDefFlags.empty.copy(isModMember = isModMember), annotations) + TermDefFlags.empty.copy(isModMember = isModMember), qualifiers) sym.defn = S(tdf) // indicates if the function really returns a module @@ -734,7 +734,7 @@ extends Importer: tdf go(sts, Nil, tdf :: acc) case L(d) => - reportUnusedAnnotations + reportUnusedQualifiers raise(d) go(sts, Nil, acc) case (td @ TypeDef(k, head, extension, body)) :: sts => @@ -787,7 +787,7 @@ extends Importer: assert(body.isEmpty) val d = given Ctx = newCtx - semantics.TypeDef(alsSym, tps, extension.map(term(_)), N, annotations) + semantics.TypeDef(alsSym, tps, extension.map(term(_)), N, qualifiers) alsSym.defn = S(d) d case Pat => @@ -798,7 +798,7 @@ extends Importer: log(s"pattern body is ${td.extension}") val translate = new ucs.Translator(this) val bod = translate(ps.map(_.params).getOrElse(Nil), td.extension.getOrElse(die)) - val pd = PatternDef(owner, patSym, tps, ps, ObjBody(Term.Blk(bod, Term.Lit(UnitLit(true)))), annotations) + val pd = PatternDef(owner, patSym, tps, ps, ObjBody(Term.Blk(bod, Term.Lit(UnitLit(true)))), qualifiers) patSym.defn = S(pd) pd case k: (Mod.type | Obj.type) => @@ -812,7 +812,7 @@ extends Importer: // case S(t) => block(t :: Nil) case S(t) => ??? case N => (new Term.Blk(Nil, Term.Lit(UnitLit(true))), ctx) - ModuleDef(owner, clsSym, tps, ps, k, ObjBody(bod), annotations) + ModuleDef(owner, clsSym, tps, ps, k, ObjBody(bod), qualifiers) clsSym.defn = S(cd) cd case Cls => @@ -826,7 +826,7 @@ extends Importer: // case S(t) => block(t :: Nil) case S(t) => ??? case N => (new Term.Blk(Nil, Term.Lit(UnitLit(true))), ctx) - ClassDef(owner, Cls, clsSym, tps, ps, ObjBody(bod), annotations) + ClassDef(owner, Cls, clsSym, tps, ps, ObjBody(bod), qualifiers) clsSym.defn = S(cd) cd go(sts, Nil, defn :: acc) @@ -834,20 +834,18 @@ extends Importer: case Modified(Keyword.`abstract`, absLoc, body) :: sts => ??? // TODO: pass abstract to `go` - go(body :: sts, annotations, acc) + go(body :: sts, qualifiers, acc) case Modified(Keyword.`declare`, absLoc, body) :: sts => // TODO: pass declare to `go` - go(body :: sts, annotations, acc) - case Annotated(prefix, receiver) :: sts => - // TODO: pass annotations to `go` - val newAnnotations = annotations :+ term(prefix) - go(receiver :: sts, newAnnotations, acc) + go(body :: sts, qualifiers, acc) + case Annotated(qualifier, target) :: sts => + go(target :: sts, qualifiers :+ term(qualifier), acc) case (result: Tree) :: Nil => - reportUnusedAnnotations + reportUnusedQualifiers val res = term(result) (Term.Blk(acc.reverse, res), ctx) case (st: Tree) :: sts => - reportUnusedAnnotations + reportUnusedQualifiers val res = term(st) // TODO reject plain term statements? Currently, `(1, 2)` is allowed to elaborate (tho it should be rejected in type checking later) go(sts, Nil, res :: acc) end go diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index ca0c80113..46384b714 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -40,7 +40,7 @@ enum Term extends Statement: case Throw(result: Term) case Try(body: Term, finallyDo: Term) case Handle(lhs: LocalSymbol, rhs: Term, defs: Ls[HandlerTermDefinition]) - case Annotated(prefix: Term, receiver: Term) + case Annotated(qualifier: Term, target: Term) lazy val symbol: Opt[Symbol] = this match case Ref(sym) => S(sym) @@ -76,7 +76,7 @@ enum Term extends Statement: case SetRef(ref, value) => "mutable reference assignment" case Deref(ref) => "dereference" case Throw(e) => "throw" - case Annotated(prefix, receiver) => "annotation" + case Annotated(qualifier, target) => "annotation" end Term import Term.* @@ -132,7 +132,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: case Try(body, finallyDo) => body :: finallyDo :: Nil case Handle(lhs, rhs, defs) => rhs :: defs.flatMap(_.td.subTerms) case Neg(e) => e :: Nil - case Annotated(prefix, receiver) => prefix :: receiver :: Nil + case Annotated(qualifier, target) => qualifier :: target :: Nil protected def children: Ls[Located] = this match case t: Lit => t.lit.asTree :: Nil @@ -204,7 +204,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: cls.tparams.map(_.showDbg).mkStringOr(", ", "[", "]")}${ cls.paramsOpt.fold("")(_.toString)} ${cls.body}" case Import(sym, file) => s"import ${sym} from ${file}" - case Annotated(prefix, receiver) => s"@${prefix.showDbg} ${receiver.showDbg}" + case Annotated(qualifier, target) => s"@${qualifier.showDbg} ${target.showDbg}" final case class LetDecl(sym: LocalSymbol, annotations: Ls[Term]) extends Statement diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index cb477e3e5..8968d1862 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -104,7 +104,7 @@ object Parser: extension (trees: Ls[Tree]) /** Note that the innermost annotation is the leftmost. */ def annotate(tree: Tree): Tree = trees.foldLeft(tree): - case (acc, ann) => Annotated(ann, acc) + case (target, qualifier) => Annotated(qualifier, target) end Parser import Parser._ @@ -239,22 +239,22 @@ abstract class Parser( def block(allowNewlines: Bool)(using Line): Ls[Tree] = blockOf(prefixRules, Nil, allowNewlines) - def blockOf(rule: ParseRule[Tree], headAnnotations: Ls[Tree], allowNewlines: Bool)(using Line): Ls[Tree] = - wrap(rule.name)(blockOfImpl(rule, headAnnotations, allowNewlines)) - def blockOfImpl(rule: ParseRule[Tree], headAnnotations: Ls[Tree], allowNewlines: Bool): Ls[Tree] = - def blockContOf(rule: ParseRule[Tree], headAnnotations: Ls[Tree] = Nil): Ls[Tree] = + def blockOf(rule: ParseRule[Tree], qualifiers: Ls[Tree], allowNewlines: Bool)(using Line): Ls[Tree] = + wrap(rule.name)(blockOfImpl(rule, qualifiers, allowNewlines)) + def blockOfImpl(rule: ParseRule[Tree], qualifiers: Ls[Tree], allowNewlines: Bool): Ls[Tree] = + def blockContOf(rule: ParseRule[Tree], qualifiers: Ls[Tree] = Nil): Ls[Tree] = yeetSpaces match - case (COMMA, _) :: _ => consume; blockOf(rule, headAnnotations, allowNewlines) - case (SEMI, _) :: _ => consume; blockOf(rule, headAnnotations, allowNewlines) - case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, headAnnotations, allowNewlines) + case (COMMA, _) :: _ => consume; blockOf(rule, qualifiers, allowNewlines) + case (SEMI, _) :: _ => consume; blockOf(rule, qualifiers, allowNewlines) + case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, qualifiers, allowNewlines) case _ => Nil cur match case Nil => Nil - case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, headAnnotations, allowNewlines) - case (SPACE, _) :: _ => consume; blockOf(rule, headAnnotations, allowNewlines) + case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, qualifiers, allowNewlines) + case (SPACE, _) :: _ => consume; blockOf(rule, qualifiers, allowNewlines) case (IDENT("@", _), l0) :: _ => consume - blockOf(rule, simpleExpr(AppPrec) :: headAnnotations, allowNewlines) + blockOf(rule, simpleExpr(AppPrec) :: qualifiers, allowNewlines) case (tok @ (id: IDENT), loc) :: _ => Keyword.all.get(id.name) match case S(kw) => @@ -268,10 +268,10 @@ abstract class Parser( if blk.isEmpty then err((msg"Expected ${subRule.whatComesAfter} ${subRule.mkAfterStr}; found end of block instead" -> S(loc) :: Nil)) errExpr - blk.map(headAnnotations.annotate) ::: blockContOf(rule) // TODO: apply headAnnotations + blk.map(qualifiers.annotate) ::: blockContOf(rule) // TODO: apply qualifiers case _ => val res = parseRule(CommaPrecNext, subRule).getOrElse(errExpr) - headAnnotations.annotate(exprCont(res, CommaPrecNext, false)) :: blockContOf(rule) + qualifiers.annotate(exprCont(res, CommaPrecNext, false)) :: blockContOf(rule) case N => // TODO dedup this common-looking logic: @@ -281,7 +281,7 @@ abstract class Parser( yeetSpaces match case (tok @ BRACKETS(Indent | Curly, toks), loc) :: _ /* if subRule.blkAlt.isEmpty */ => consume - if headAnnotations.nonEmpty then + if qualifiers.nonEmpty then err((msg"Blocks cannot be annotated" -> S(loc) :: Nil)) prefixRules.kwAlts.get(kw.name) match case S(subRule) if subRule.blkAlt.isEmpty => @@ -294,14 +294,14 @@ abstract class Parser( prefixRules.kwAlts.get(kw.name) match case S(subRule) => val e = parseRule(CommaPrecNext, subRule).getOrElse(errExpr) - headAnnotations.annotate(parseRule(CommaPrecNext, exprAlt.rest).map(res => exprAlt.k(e, res)).getOrElse(errExpr)) :: blockContOf(rule) + qualifiers.annotate(parseRule(CommaPrecNext, exprAlt.rest).map(res => exprAlt.k(e, res)).getOrElse(errExpr)) :: blockContOf(rule) case N => // TODO dedup? err((msg"Expected ${rule.whatComesAfter} ${rule.mkAfterStr}; found ${tok.describe} instead" -> S(loc) :: Nil)) - headAnnotations.annotate(errExpr) :: blockContOf(rule) + qualifiers.annotate(errExpr) :: blockContOf(rule) case N => err((msg"Expected ${rule.whatComesAfter} ${rule.mkAfterStr}; found ${tok.describe} instead" -> S(loc) :: Nil)) - headAnnotations.annotate(errExpr) :: blockContOf(rule) + qualifiers.annotate(errExpr) :: blockContOf(rule) case N => val lhs = tryParseExp(CommaPrecNext, tok, loc, rule).getOrElse(errExpr) cur match @@ -310,9 +310,9 @@ abstract class Parser( val rhs = expr(CommaPrecNext) Def(lhs, rhs) :: blockContOf(rule) case _ => - headAnnotations.annotate(lhs) :: blockContOf(rule) + qualifiers.annotate(lhs) :: blockContOf(rule) case (tok, loc) :: _ => - headAnnotations.annotate(tryParseExp(CommaPrecNext, tok, loc, rule).getOrElse(errExpr)) :: blockContOf(rule) + qualifiers.annotate(tryParseExp(CommaPrecNext, tok, loc, rule).getOrElse(errExpr)) :: blockContOf(rule) private def tryParseExp[A](prec: Int, tok: Token, loc: Loc, rule: ParseRule[A]): Opt[A] = @@ -479,8 +479,8 @@ abstract class Parser( yeetSpaces match case (IDENT("@", _), l0) :: _ => consume - val ann = simpleExpr(AppPrec) - Annotated(ann, simpleExpr(prec)) + val qualifier = simpleExpr(AppPrec) + Annotated(qualifier, simpleExpr(prec)) case (IDENT(nme, sym), loc) :: _ => Keyword.all.get(nme) match case S(kw) => // * Expressions starting with keywords should be handled in parseRule diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 6f78d597b..c5761e23d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -76,7 +76,7 @@ enum Tree extends AutoLocated: case RegRef(reg: Tree, value: Tree) case Effectful(eff: Tree, body: Tree) case Spread(kw: Keyword.Ellipsis, kwLoc: Opt[Loc], body: Opt[Tree]) - case Annotated(prefix: Tree, receiver: Tree) + case Annotated(qualifier: Tree, target: Tree) def children: Ls[Tree] = this match case _: Empty | _: Error | _: Ident | _: Literal | _: Under => Nil @@ -110,7 +110,7 @@ enum Tree extends AutoLocated: case Open(bod) => bod :: Nil case Def(lhs, rhs) => lhs :: rhs :: Nil case Spread(_, _, body) => body.toList - case Annotated(prefix, receiver) => prefix :: receiver :: Nil + case Annotated(qualifier, target) => qualifier :: target :: Nil def describe: Str = this match case Empty() => "empty" @@ -147,7 +147,7 @@ enum Tree extends AutoLocated: case Handle(_, _, _, _) => "handle" case Def(lhs, rhs) => "defining assignment" case Spread(_, _, _) => "spread" - case Annotated(prefix, receiver) => "annotated" + case Annotated(_, _) => "annotated" def showDbg: Str = toString // TODO @@ -208,7 +208,7 @@ object Apps: object Annotations: def unapply(t: Tree): Opt[(Ls[Tree], Tree)] = t match - case Annotated(p, Annotations(ps, recv)) => S(p :: ps, recv) + case Annotated(q, Annotations(qs, target)) => S(q :: qs, target) case other => S((Nil, other)) /** Matches applications with underscores in some argument and/or prefix positions. */ diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls index e5288d9df..105b3cee8 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls @@ -51,3 +51,28 @@ let failure = 1 //│ failure = 1 //│ success = 0 + +// Multiple annotations +// ==================== + +// :pt +@inline @tailrec fun fib(n) = + if n < 2 then n else fib(n - 1) + fib(n - 2) + +module internal + +// :pt +@internal object + shared + thread_local + transient + volatile + +// :pt +@volatile let + @shared counter = 0 + @transient @thread_local cache = () + @volatile @transient empty = "" + normal = () +//│ counter = 0 +//│ empty = '' diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls new file mode 100644 index 000000000..402ebd0d2 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls @@ -0,0 +1,72 @@ +// The annotations for the following expressions are not supported for now. + +module debug + +:w +@debug 0 +//│ ╔══[WARNING] The annotation qualifier is not applied +//│ ║ l.6: @debug 0 +//│ ║ ^^^^^ +//│ ╟── Because annotations are not supported for integer literal +//│ ║ l.6: @debug 0 +//│ ╙── ^ + +:w +@debug 1 + 2 +//│ ╔══[WARNING] The annotation qualifier is not applied +//│ ║ l.15: @debug 1 + 2 +//│ ║ ^^^^^ +//│ ╟── Because annotations are not supported for application +//│ ║ l.15: @debug 1 + 2 +//│ ╙── ^^^^^ + +:w +(@debug 1 + 2) +//│ ╔══[WARNING] The annotation qualifier is not applied +//│ ║ l.24: (@debug 1 + 2) +//│ ║ ^^^^^ +//│ ╟── Because annotations are not supported for application +//│ ║ l.24: (@debug 1 + 2) +//│ ╙── ^^^^^ + +:w +(@debug 1) + 2 +//│ ╔══[WARNING] This annotation qualifier is not applied +//│ ║ l.33: (@debug 1) + 2 +//│ ║ ^^^^^ +//│ ╟── Because annotations are not supported for integer literal +//│ ║ l.33: (@debug 1) + 2 +//│ ╙── ^ + +:w +(1 + @debug 2) +//│ ╔══[WARNING] This annotation qualifier is not applied +//│ ║ l.42: (1 + @debug 2) +//│ ║ ^^^^^ +//│ ╟── Because annotations are not supported for integer literal +//│ ║ l.42: (1 + @debug 2) +//│ ╙── ^ + +class Log(msg: Str) + +:w +id(@Log 5) +//│ ╔══[WARNING] This annotation qualifier is not applied +//│ ║ l.53: id(@Log 5) +//│ ║ ^^^ +//│ ╟── Because annotations are not supported for integer literal +//│ ║ l.53: id(@Log 5) +//│ ╙── ^ + +:pe +:w +@1 + 2 class Qux +//│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead +//│ ║ l.63: @1 + 2 class Qux +//│ ╙── ^^^^^ +//│ ╔══[WARNING] The annotation qualifier is not applied +//│ ║ l.63: @1 + 2 class Qux +//│ ║ ^ +//│ ╟── Because annotations are not supported for application +//│ ║ l.63: @1 + 2 class Qux +//│ ╙── ^^^ diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Useless.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Useless.mls deleted file mode 100644 index e8762eb96..000000000 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Useless.mls +++ /dev/null @@ -1,70 +0,0 @@ -module debug - -:w -@debug 0 -//│ ╔══[WARNING] The annotation is not used -//│ ║ l.4: @debug 0 -//│ ║ ^^^^^ -//│ ╟── Because annotations cannot be applied to integer literal -//│ ║ l.4: @debug 0 -//│ ╙── ^ - -:w -@debug 1 + 2 -//│ ╔══[WARNING] The annotation is not used -//│ ║ l.13: @debug 1 + 2 -//│ ║ ^^^^^ -//│ ╟── Because annotations cannot be applied to application -//│ ║ l.13: @debug 1 + 2 -//│ ╙── ^^^^^ - -:w -(@debug 1 + 2) -//│ ╔══[WARNING] The annotation is not used -//│ ║ l.22: (@debug 1 + 2) -//│ ║ ^^^^^ -//│ ╟── Because annotations cannot be applied to application -//│ ║ l.22: (@debug 1 + 2) -//│ ╙── ^^^^^ - -:w -(@debug 1) + 2 -//│ ╔══[WARNING] The annotation is not used -//│ ║ l.31: (@debug 1) + 2 -//│ ║ ^^^^^ -//│ ╟── Because annotations cannot be applied to integer literal -//│ ║ l.31: (@debug 1) + 2 -//│ ╙── ^ - -:w -(1 + @debug 2) -//│ ╔══[WARNING] The annotation is not used -//│ ║ l.40: (1 + @debug 2) -//│ ║ ^^^^^ -//│ ╟── Because annotations cannot be applied to integer literal -//│ ║ l.40: (1 + @debug 2) -//│ ╙── ^ - -class Log(msg: Str) - -:w -id(@Log 5) -//│ ╔══[WARNING] The annotation is not used -//│ ║ l.51: id(@Log 5) -//│ ║ ^^^ -//│ ╟── Because annotations cannot be applied to integer literal -//│ ║ l.51: id(@Log 5) -//│ ╙── ^ - -:pe -:w -@1 + 2 class Qux -//│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead -//│ ║ l.61: @1 + 2 class Qux -//│ ╙── ^^^^^ -//│ ╔══[WARNING] The annotation is not used -//│ ║ l.61: @1 + 2 class Qux -//│ ║ ^ -//│ ╟── Because annotations cannot be applied to application -//│ ║ l.61: @1 + 2 class Qux -//│ ╙── ^^^ From ac010136ad567704c7e9e79e81d775f7eea152b1 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Wed, 8 Jan 2025 17:18:54 +0800 Subject: [PATCH 06/16] `TypeLikeDef` should have `annotations` as a member --- hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 46384b714..248e8fa4e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -254,6 +254,7 @@ sealed trait Companion extends Definition sealed abstract class TypeLikeDef extends Definition: val tparams: Ls[TyParam] + val annotations: Ls[Term] sealed abstract class ClassLikeDef extends TypeLikeDef: val owner: Opt[InnerSymbol] From 7364abbbcf890cbc49c1a68cd101a712c2022983 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Thu, 9 Jan 2025 10:42:56 +0800 Subject: [PATCH 07/16] Update warning messages Co-authored-by: Lionel Parreaux --- hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 1b548f67b..50efeaee5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -469,8 +469,8 @@ extends Importer: Term.Error case Annotated(lhs, rhs) => raise(WarningReport( - msg"This annotation qualifier is not applied" -> lhs.toLoc :: - msg"Because annotations are not supported for ${rhs.describe}" -> rhs.toLoc :: Nil)) + msg"This annotation has no effect." -> lhs.toLoc :: + msg"Annotations are not supported on ${rhs.describe} terms." -> rhs.toLoc :: Nil)) val qualifier = lhs match case App(_: (Ident | SynthSel | Sel), _) | _: (Ident | SynthSel | Sel) => term(lhs) case _ => From ac7a0eb1c67637dab013b070b41e6e434967a8ec Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Thu, 9 Jan 2025 11:00:41 +0800 Subject: [PATCH 08/16] Fix minor --- .../main/scala/hkmc2/codegen/Lowering.scala | 6 +++-- .../scala/hkmc2/semantics/Elaborator.scala | 6 ++--- .../src/main/scala/hkmc2/syntax/Parser.scala | 2 +- .../syntax/annotations/Declarations.mls | 3 --- .../syntax/annotations/Unsupported.mls | 27 +++++-------------- 5 files changed, 14 insertions(+), 30 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index a98f7eb44..146440890 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -309,7 +309,7 @@ class Lowering(using TL, Raise, Elaborator.State): term(finallyDo)(_ => End()), k(Value.Ref(l)) ) - + case Handle(lhs, rhs, defs) => raise(ErrorReport( msg"Effect handlers are not enabled" -> @@ -338,7 +338,9 @@ class Lowering(using TL, Raise, Elaborator.State): AssignField(ref, Tree.Ident("value"), value, k(value))(N) case Annotated(prefix, receiver) => - // TODO: handle annotations + raise(WarningReport( + msg"This annotation has no effect." -> prefix.toLoc :: + msg"Annotations are not supported on ${receiver.describe} terms." -> receiver.toLoc :: Nil)) term(receiver)(k) case Error => End("error") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 50efeaee5..b3d2aae9d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -468,9 +468,9 @@ extends Importer: raise(ErrorReport(msg"Illegal position for '_' placeholder." -> tree.toLoc :: Nil)) Term.Error case Annotated(lhs, rhs) => - raise(WarningReport( - msg"This annotation has no effect." -> lhs.toLoc :: - msg"Annotations are not supported on ${rhs.describe} terms." -> rhs.toLoc :: Nil)) + // raise(WarningReport( + // msg"This annotation has no effect." -> lhs.toLoc :: + // msg"Annotations are not supported on ${rhs.describe} terms." -> rhs.toLoc :: Nil)) val qualifier = lhs match case App(_: (Ident | SynthSel | Sel), _) | _: (Ident | SynthSel | Sel) => term(lhs) case _ => diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index 8968d1862..93d22d976 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -268,7 +268,7 @@ abstract class Parser( if blk.isEmpty then err((msg"Expected ${subRule.whatComesAfter} ${subRule.mkAfterStr}; found end of block instead" -> S(loc) :: Nil)) errExpr - blk.map(qualifiers.annotate) ::: blockContOf(rule) // TODO: apply qualifiers + blk.map(qualifiers.annotate) ::: blockContOf(rule) case _ => val res = parseRule(CommaPrecNext, subRule).getOrElse(errExpr) qualifiers.annotate(exprCont(res, CommaPrecNext, false)) :: blockContOf(rule) diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls index 105b3cee8..652809a42 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls @@ -55,20 +55,17 @@ let // Multiple annotations // ==================== -// :pt @inline @tailrec fun fib(n) = if n < 2 then n else fib(n - 1) + fib(n - 2) module internal -// :pt @internal object shared thread_local transient volatile -// :pt @volatile let @shared counter = 0 @transient @thread_local cache = () diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls index 402ebd0d2..d9bf00715 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls @@ -31,42 +31,27 @@ module debug :w (@debug 1) + 2 -//│ ╔══[WARNING] This annotation qualifier is not applied -//│ ║ l.33: (@debug 1) + 2 -//│ ║ ^^^^^ -//│ ╟── Because annotations are not supported for integer literal -//│ ║ l.33: (@debug 1) + 2 -//│ ╙── ^ +//│ FAILURE: Unexpected lack of warnings :w (1 + @debug 2) -//│ ╔══[WARNING] This annotation qualifier is not applied -//│ ║ l.42: (1 + @debug 2) -//│ ║ ^^^^^ -//│ ╟── Because annotations are not supported for integer literal -//│ ║ l.42: (1 + @debug 2) -//│ ╙── ^ +//│ FAILURE: Unexpected lack of warnings class Log(msg: Str) :w id(@Log 5) -//│ ╔══[WARNING] This annotation qualifier is not applied -//│ ║ l.53: id(@Log 5) -//│ ║ ^^^ -//│ ╟── Because annotations are not supported for integer literal -//│ ║ l.53: id(@Log 5) -//│ ╙── ^ +//│ FAILURE: Unexpected lack of warnings :pe :w @1 + 2 class Qux //│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead -//│ ║ l.63: @1 + 2 class Qux +//│ ║ l.48: @1 + 2 class Qux //│ ╙── ^^^^^ //│ ╔══[WARNING] The annotation qualifier is not applied -//│ ║ l.63: @1 + 2 class Qux +//│ ║ l.48: @1 + 2 class Qux //│ ║ ^ //│ ╟── Because annotations are not supported for application -//│ ║ l.63: @1 + 2 class Qux +//│ ║ l.48: @1 + 2 class Qux //│ ╙── ^^^ From 82cf38f48fc7a583b3b24ec0d634686ae4105359 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Thu, 9 Jan 2025 12:27:23 +0800 Subject: [PATCH 09/16] Report useless annotations in `Lowering` --- .../main/scala/hkmc2/codegen/Lowering.scala | 20 ++- .../scala/hkmc2/semantics/Elaborator.scala | 9 +- .../src/main/scala/hkmc2/syntax/Tree.scala | 3 +- .../syntax/annotations/Declarations.mls | 137 +++++++++++++++++- .../syntax/annotations/Unsupported.mls | 63 +++++--- 5 files changed, 200 insertions(+), 32 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 146440890..56ae44193 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -102,6 +102,7 @@ class Lowering(using TL, Raise, Elaborator.State): case st.Blk((d: Declaration) :: stats, res) => d match case td: TermDefinition => + reportAnnotations(td, td.annotations) td.body match case N => // abstract declarations have no lowering term(st.Blk(stats, res))(k) @@ -120,12 +121,15 @@ class Lowering(using TL, Raise, Elaborator.State): term(st.Blk(stats, res))(k)) // case cls: ClassDef => case cls: ClassLikeDef => + reportAnnotations(cls, cls.annotations) val bodBlk = cls.body.blk val (mtds, rest1) = bodBlk.stats.partitionMap: case td: TermDefinition if td.k is syntax.Fun => L(td) case s => R(s) val (privateFlds, rest2) = rest1.partitionMap: - case LetDecl(sym: TermSymbol, _) => L(sym) + case decl @ LetDecl(sym: TermSymbol, annotations) => + reportAnnotations(decl, annotations) + L(sym) case s => R(s) val publicFlds = rest2.collect: case td @ TermDefinition(k = (_: syntax.Val)) => td @@ -146,7 +150,8 @@ class Lowering(using TL, Raise, Elaborator.State): case _ => // TODO handle term(st.Blk(stats, res))(k) - case st.Blk((LetDecl(sym, _)) :: stats, res) => + case st.Blk((decl @ LetDecl(sym, annotations)) :: stats, res) => + reportAnnotations(decl, annotations) term(st.Blk(stats, res))(k) case st.Blk((DefineVar(sym, rhs)) :: stats, res) => subTerm(rhs): r => @@ -378,6 +383,17 @@ class Lowering(using TL, Raise, Elaborator.State): def setupFunctionDef(paramLists: List[ParamList], bodyTerm: Term, name: Option[Str])(using Subst): (List[ParamList], Block) = (paramLists, returnedTerm(bodyTerm)) + + def reportAnnotations(target: Statement, annotations: Ls[Term]): Unit = if annotations.nonEmpty then + raise(WarningReport( + (msg"This annotation has no effect." -> annotations.foldLeft[Opt[Loc]](N): + case (acc, term) => acc match + case N => term.toLoc + case S(loc) => S(loc ++ term.toLoc)) :: + msg"Annotations are not supported on this ${target match + case _: LetDecl => "let declaration" + case td: TermDefinition => td.k.desc + case cls: ClassLikeDef => cls.kind.desc }." -> target.toLoc :: Nil)) trait LoweringSelSanityChecks diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index b3d2aae9d..acc9b6f1b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -468,9 +468,6 @@ extends Importer: raise(ErrorReport(msg"Illegal position for '_' placeholder." -> tree.toLoc :: Nil)) Term.Error case Annotated(lhs, rhs) => - // raise(WarningReport( - // msg"This annotation has no effect." -> lhs.toLoc :: - // msg"Annotations are not supported on ${rhs.describe} terms." -> rhs.toLoc :: Nil)) val qualifier = lhs match case App(_: (Ident | SynthSel | Sel), _) | _: (Ident | SynthSel | Sel) => term(lhs) case _ => @@ -536,13 +533,13 @@ extends Importer: /** Call this function when the following term cannot be annotated. */ def reportUnusedQualifiers: Unit = if qualifiers.nonEmpty then raise: WarningReport: - msg"The annotation qualifier is not applied" -> (qualifiers.foldLeft[Opt[Loc]](N): + msg"This annotation has no effect" -> (qualifiers.foldLeft[Opt[Loc]](N): case (acc, ann) => acc match case N => ann.toLoc case S(loc) => S(loc ++ ann.toLoc) ) :: (sts.headOption match - case N => msg"Because the target term is missing" -> blk.toLoc.map(_.right) - case S(head) => msg"Because annotations are not supported for ${head.describe}" -> head.toLoc + case N => msg"A target term is expected at the end of block" -> blk.toLoc.map(_.right) + case S(head) => msg"Annotations are not supported on ${head.describe}" -> head.toLoc ) :: Nil sts match case Nil => diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index c5761e23d..ef87fe565 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -238,7 +238,8 @@ case object ParamBind extends ValLike("", "parameter") case object Fun extends TermDefKind("fun", "function") sealed abstract class TypeDefKind(desc: Str) extends DeclKind(desc) sealed trait ObjDefKind -sealed trait ClsLikeKind extends ObjDefKind +sealed trait ClsLikeKind extends ObjDefKind: + val desc: Str case object Cls extends TypeDefKind("class") with ClsLikeKind case object Trt extends TypeDefKind("trait") with ObjDefKind case object Mxn extends TypeDefKind("mixin") diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls index 652809a42..1def777fb 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls @@ -5,71 +5,202 @@ module tailrec tailrec //│ = tailrec { class: [class tailrec] } +:w @tailrec fun fact_n(n, acc) = if n == 0 then acc else fact_n(n - 1, n * acc) - +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.9: @tailrec fun fact_n(n, acc) = +//│ ║ ^^^^^^^ +//│ ╟── Annotations are not supported on this function. +//│ ║ l.10: if n == 0 then acc else fact_n(n - 1, n * acc) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:w fun fact(n) = @tailrec fun go(n, acc) = if n == 0 then acc else go(n - 1, n * acc) go(n, 1) +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.20: @tailrec fun go(n, acc) = +//│ ║ ^^^^^^^ +//│ ╟── Annotations are not supported on this function. +//│ ║ l.21: if n == 0 then acc else go(n - 1, n * acc) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ class Freezed(degree: Num) +:w @Freezed(-273.15) class AbsoluteZero +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.33: @Freezed(-273.15) class AbsoluteZero +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╙── Annotations are not supported on this class. +:w @Freezed(-18) class Beverage(name: Str) - +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.40: @Freezed(-18) class Beverage(name: Str) +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Annotations are not supported on this class. +//│ ║ l.40: @Freezed(-18) class Beverage(name: Str) +//│ ╙── ^^^ + +:w @Freezed(-4) let drink = Beverage("Coke") +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.49: @Freezed(-4) let drink = Beverage("Coke") +//│ ║ ^^^^^^^^^^^ +//│ ╟── Annotations are not supported on this let declaration. +//│ ║ l.49: @Freezed(-4) let drink = Beverage("Coke") +//│ ╙── ^^^^^^^^^^^ //│ drink = Beverage { name: 'Coke' } module Foo with class Bar(qax: Str) +:w @Foo.Bar("baz") class Qux +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.62: @Foo.Bar("baz") class Qux +//│ ║ ^^^^^^^^^^^^^^ +//│ ╙── Annotations are not supported on this class. +:w @42 class Qux +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.69: @42 class Qux +//│ ║ ^^ +//│ ╙── Annotations are not supported on this class. +:w @(1 + 2) class Qux +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.76: @(1 + 2) class Qux +//│ ║ ^^^^^ +//│ ╙── Annotations are not supported on this class. module inline +:w // All functions are annotated with @inline. @inline fun min(x, y) = if x < y then x else y max(x, y) = if x > y then x else y - +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.86: @inline +//│ ║ ^^^^^^ +//│ ╟── Annotations are not supported on this function. +//│ ║ l.88: min(x, y) = if x < y then x else y +//│ ╙── ^^^^^^^^^^^^^^^^^^^ +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.86: @inline +//│ ║ ^^^^^^ +//│ ╟── Annotations are not supported on this function. +//│ ║ l.89: max(x, y) = if x > y then x else y +//│ ╙── ^^^^^^^^^^^^^^^^^^^ + +:w @inline let abs(x) = if x < 0 then -x else x clamp(x, lo, hi) = min(max(x, lo), hi) +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.104: @inline let +//│ ║ ^^^^^^ +//│ ╙── Annotations are not supported on this let declaration. +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.104: @inline let +//│ ║ ^^^^^^ +//│ ╙── Annotations are not supported on this let declaration. //│ abs = [Function (anonymous)] //│ clamp = [Function (anonymous)] +:w // Only the first variable is annotated with @inline. let @inline success = 0 failure = 1 +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.121: @inline success = 0 +//│ ║ ^^^^^^ +//│ ╙── Annotations are not supported on this let declaration. //│ failure = 1 //│ success = 0 // Multiple annotations // ==================== +:w @inline @tailrec fun fib(n) = if n < 2 then n else fib(n - 1) + fib(n - 2) +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.134: @inline @tailrec fun fib(n) = +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Annotations are not supported on this function. +//│ ║ l.135: if n < 2 then n else fib(n - 1) + fib(n - 2) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ module internal +:w @internal object shared thread_local transient volatile +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.146: @internal object +//│ ║ ^^^^^^^^ +//│ ╙── Annotations are not supported on this object. +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.146: @internal object +//│ ║ ^^^^^^^^ +//│ ╙── Annotations are not supported on this object. +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.146: @internal object +//│ ║ ^^^^^^^^ +//│ ╙── Annotations are not supported on this object. +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.146: @internal object +//│ ║ ^^^^^^^^ +//│ ╙── Annotations are not supported on this object. @volatile let @shared counter = 0 @transient @thread_local cache = () @volatile @transient empty = "" normal = () +//│ FAILURE: Unexpected warning +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.168: @volatile let +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.169: @shared counter = 0 +//│ ║ ^^^^^^^^^ +//│ ╙── Annotations are not supported on this let declaration. +//│ FAILURE: Unexpected warning +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.168: @volatile let +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.169: @shared counter = 0 +//│ ║ ^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.170: @transient @thread_local cache = () +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── Annotations are not supported on this let declaration. +//│ FAILURE: Unexpected warning +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.168: @volatile let +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.169: @shared counter = 0 +//│ ║ ^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.170: @transient @thread_local cache = () +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.171: @volatile @transient empty = "" +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── Annotations are not supported on this let declaration. +//│ FAILURE: Unexpected warning +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.168: @volatile let +//│ ║ ^^^^^^^^ +//│ ╙── Annotations are not supported on this let declaration. //│ counter = 0 //│ empty = '' diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls index d9bf00715..c3e4f5d1e 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls @@ -1,57 +1,80 @@ +:js // The annotations for the following expressions are not supported for now. module debug :w @debug 0 -//│ ╔══[WARNING] The annotation qualifier is not applied -//│ ║ l.6: @debug 0 +//│ ╔══[WARNING] This annotation has no effect +//│ ║ l.7: @debug 0 //│ ║ ^^^^^ -//│ ╟── Because annotations are not supported for integer literal -//│ ║ l.6: @debug 0 +//│ ╟── Annotations are not supported on integer literal +//│ ║ l.7: @debug 0 //│ ╙── ^ +//│ = 0 :w @debug 1 + 2 -//│ ╔══[WARNING] The annotation qualifier is not applied -//│ ║ l.15: @debug 1 + 2 +//│ ╔══[WARNING] This annotation has no effect +//│ ║ l.17: @debug 1 + 2 //│ ║ ^^^^^ -//│ ╟── Because annotations are not supported for application -//│ ║ l.15: @debug 1 + 2 +//│ ╟── Annotations are not supported on application +//│ ║ l.17: @debug 1 + 2 //│ ╙── ^^^^^ +//│ = 3 :w (@debug 1 + 2) -//│ ╔══[WARNING] The annotation qualifier is not applied -//│ ║ l.24: (@debug 1 + 2) +//│ ╔══[WARNING] This annotation has no effect +//│ ║ l.27: (@debug 1 + 2) //│ ║ ^^^^^ -//│ ╟── Because annotations are not supported for application -//│ ║ l.24: (@debug 1 + 2) +//│ ╟── Annotations are not supported on application +//│ ║ l.27: (@debug 1 + 2) //│ ╙── ^^^^^ +//│ = 3 :w (@debug 1) + 2 -//│ FAILURE: Unexpected lack of warnings +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.37: (@debug 1) + 2 +//│ ║ ^^^^^ +//│ ╟── Annotations are not supported on integer literal terms. +//│ ║ l.37: (@debug 1) + 2 +//│ ╙── ^ +//│ = 3 :w (1 + @debug 2) -//│ FAILURE: Unexpected lack of warnings +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.47: (1 + @debug 2) +//│ ║ ^^^^^ +//│ ╟── Annotations are not supported on integer literal terms. +//│ ║ l.47: (1 + @debug 2) +//│ ╙── ^ +//│ = 3 class Log(msg: Str) :w id(@Log 5) -//│ FAILURE: Unexpected lack of warnings +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.59: id(@Log 5) +//│ ║ ^^^ +//│ ╟── Annotations are not supported on integer literal terms. +//│ ║ l.59: id(@Log 5) +//│ ╙── ^ +//│ = 5 :pe :w @1 + 2 class Qux //│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead -//│ ║ l.48: @1 + 2 class Qux +//│ ║ l.70: @1 + 2 class Qux //│ ╙── ^^^^^ -//│ ╔══[WARNING] The annotation qualifier is not applied -//│ ║ l.48: @1 + 2 class Qux +//│ ╔══[WARNING] This annotation has no effect +//│ ║ l.70: @1 + 2 class Qux //│ ║ ^ -//│ ╟── Because annotations are not supported for application -//│ ║ l.48: @1 + 2 class Qux +//│ ╟── Annotations are not supported on application +//│ ║ l.70: @1 + 2 class Qux //│ ╙── ^^^ +//│ = 2 From 09a3393846ad8ef0a220be320ba8c7cab8c767c1 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Thu, 9 Jan 2025 12:31:02 +0800 Subject: [PATCH 10/16] Replace all occurrence of "qualifier" with "annotation" --- .../scala/hkmc2/semantics/Elaborator.scala | 52 +++++++++---------- .../src/main/scala/hkmc2/semantics/Term.scala | 8 +-- .../src/main/scala/hkmc2/syntax/Parser.scala | 42 +++++++-------- .../src/main/scala/hkmc2/syntax/Tree.scala | 4 +- .../syntax/annotations/Declarations.mls | 25 ++++----- 5 files changed, 64 insertions(+), 67 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index acc9b6f1b..c8a538125 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -468,12 +468,12 @@ extends Importer: raise(ErrorReport(msg"Illegal position for '_' placeholder." -> tree.toLoc :: Nil)) Term.Error case Annotated(lhs, rhs) => - val qualifier = lhs match + val annotation = lhs match case App(_: (Ident | SynthSel | Sel), _) | _: (Ident | SynthSel | Sel) => term(lhs) case _ => - raise(ErrorReport(msg"Illegal annotation qualifier shape." -> lhs.toLoc :: Nil)) + raise(ErrorReport(msg"Illegal annotation shape." -> lhs.toLoc :: Nil)) Term.Error - Term.Annotated(qualifier, term(rhs)) + Term.Annotated(annotation, term(rhs)) // case _ => // ??? @@ -529,11 +529,11 @@ extends Importer: // TODO extract this into a separate method @tailrec - def go(sts: Ls[Tree], qualifiers: Ls[Term], acc: Ls[Statement]): Ctxl[(Term.Blk, Ctx)] = + def go(sts: Ls[Tree], annotations: Ls[Term], acc: Ls[Statement]): Ctxl[(Term.Blk, Ctx)] = /** Call this function when the following term cannot be annotated. */ - def reportUnusedQualifiers: Unit = if qualifiers.nonEmpty then raise: + def reportUnusedAnnotations: Unit = if annotations.nonEmpty then raise: WarningReport: - msg"This annotation has no effect" -> (qualifiers.foldLeft[Opt[Loc]](N): + msg"This annotation has no effect" -> (annotations.foldLeft[Opt[Loc]](N): case (acc, ann) => acc match case N => ann.toLoc case S(loc) => S(loc ++ ann.toLoc) @@ -543,11 +543,11 @@ extends Importer: ) :: Nil sts match case Nil => - reportUnusedQualifiers + reportUnusedAnnotations val res = unit (Term.Blk(acc.reverse, res), ctx) case Open(bod) :: sts => - reportUnusedQualifiers + reportUnusedAnnotations bod match case Jux(bse, Block(sts)) => some(bse -> some(sts)) @@ -558,7 +558,7 @@ extends Importer: raise(ErrorReport(msg"Illegal 'open' statement shape." -> bod.toLoc :: Nil)) N match - case N => go(sts, qualifiers, acc) + case N => go(sts, annotations, acc) case S((base, importedTrees)) => base match case baseId: Ident => @@ -590,7 +590,7 @@ extends Importer: raise(ErrorReport(msg"Illegal 'open' statement base." -> base.toLoc :: Nil)) go(sts, Nil, acc) case (m @ Modified(Keyword.`import`, absLoc, arg)) :: sts => - reportUnusedQualifiers + reportUnusedAnnotations val (newCtx, newAcc) = arg match case Tree.StrLit(path) => val stmt = importPath(path) @@ -612,18 +612,18 @@ extends Importer: case S(rhs) => val rrhs = tups.foldRight(rhs): Tree.InfixApp(_, Keyword.`=>`, _) - mkLetBinding(sym, term(rrhs), qualifiers) reverse_::: acc + mkLetBinding(sym, term(rrhs), annotations) reverse_::: acc case N => if tups.nonEmpty then raise(ErrorReport(msg"Expected a right-hand side for let bindings with parameters" -> hd.toLoc :: Nil)) - LetDecl(sym, qualifiers) :: acc + LetDecl(sym, annotations) :: acc (ctx + (id.name -> sym)) givenIn: go(sts, Nil, newAcc) case (tree @ LetLike(`let`, lhs, S(rhs), N)) :: sts => raise(ErrorReport(msg"Unsupported let binding shape" -> tree.toLoc :: Nil)) go(sts, Nil, Term.Error :: acc) case (hd @ Handle(id: Ident, cls: Ident, Block(sts_), N)) :: sts => - reportUnusedQualifiers + reportUnusedAnnotations val sym = fieldOrVarSym(HandlerBind, id) log(s"Processing `handle` statement $id (${sym}) ${ctx.outer}") @@ -656,7 +656,7 @@ extends Importer: go(sts, Nil, Term.Error :: acc) case Def(lhs, rhs) :: sts => - reportUnusedQualifiers + reportUnusedAnnotations lhs match case id: Ident => val r = term(rhs) @@ -698,7 +698,7 @@ extends Importer: val b = rhs.map(term(_)(using newCtx)) val r = FlowSymbol(s"‹result of ${sym}›") val tdf = TermDefinition(owner, k, sym, pss, s, b, r, - TermDefFlags.empty.copy(isModMember = isModMember), qualifiers) + TermDefFlags.empty.copy(isModMember = isModMember), annotations) sym.defn = S(tdf) // indicates if the function really returns a module @@ -731,7 +731,7 @@ extends Importer: tdf go(sts, Nil, tdf :: acc) case L(d) => - reportUnusedQualifiers + reportUnusedAnnotations raise(d) go(sts, Nil, acc) case (td @ TypeDef(k, head, extension, body)) :: sts => @@ -784,7 +784,7 @@ extends Importer: assert(body.isEmpty) val d = given Ctx = newCtx - semantics.TypeDef(alsSym, tps, extension.map(term(_)), N, qualifiers) + semantics.TypeDef(alsSym, tps, extension.map(term(_)), N, annotations) alsSym.defn = S(d) d case Pat => @@ -795,7 +795,7 @@ extends Importer: log(s"pattern body is ${td.extension}") val translate = new ucs.Translator(this) val bod = translate(ps.map(_.params).getOrElse(Nil), td.extension.getOrElse(die)) - val pd = PatternDef(owner, patSym, tps, ps, ObjBody(Term.Blk(bod, Term.Lit(UnitLit(true)))), qualifiers) + val pd = PatternDef(owner, patSym, tps, ps, ObjBody(Term.Blk(bod, Term.Lit(UnitLit(true)))), annotations) patSym.defn = S(pd) pd case k: (Mod.type | Obj.type) => @@ -809,7 +809,7 @@ extends Importer: // case S(t) => block(t :: Nil) case S(t) => ??? case N => (new Term.Blk(Nil, Term.Lit(UnitLit(true))), ctx) - ModuleDef(owner, clsSym, tps, ps, k, ObjBody(bod), qualifiers) + ModuleDef(owner, clsSym, tps, ps, k, ObjBody(bod), annotations) clsSym.defn = S(cd) cd case Cls => @@ -823,7 +823,7 @@ extends Importer: // case S(t) => block(t :: Nil) case S(t) => ??? case N => (new Term.Blk(Nil, Term.Lit(UnitLit(true))), ctx) - ClassDef(owner, Cls, clsSym, tps, ps, ObjBody(bod), qualifiers) + ClassDef(owner, Cls, clsSym, tps, ps, ObjBody(bod), annotations) clsSym.defn = S(cd) cd go(sts, Nil, defn :: acc) @@ -831,18 +831,18 @@ extends Importer: case Modified(Keyword.`abstract`, absLoc, body) :: sts => ??? // TODO: pass abstract to `go` - go(body :: sts, qualifiers, acc) + go(body :: sts, annotations, acc) case Modified(Keyword.`declare`, absLoc, body) :: sts => // TODO: pass declare to `go` - go(body :: sts, qualifiers, acc) - case Annotated(qualifier, target) :: sts => - go(target :: sts, qualifiers :+ term(qualifier), acc) + go(body :: sts, annotations, acc) + case Annotated(annotation, target) :: sts => + go(target :: sts, annotations :+ term(annotation), acc) case (result: Tree) :: Nil => - reportUnusedQualifiers + reportUnusedAnnotations val res = term(result) (Term.Blk(acc.reverse, res), ctx) case (st: Tree) :: sts => - reportUnusedQualifiers + reportUnusedAnnotations val res = term(st) // TODO reject plain term statements? Currently, `(1, 2)` is allowed to elaborate (tho it should be rejected in type checking later) go(sts, Nil, res :: acc) end go diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 248e8fa4e..4b21ee1b5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -40,7 +40,7 @@ enum Term extends Statement: case Throw(result: Term) case Try(body: Term, finallyDo: Term) case Handle(lhs: LocalSymbol, rhs: Term, defs: Ls[HandlerTermDefinition]) - case Annotated(qualifier: Term, target: Term) + case Annotated(annotation: Term, target: Term) lazy val symbol: Opt[Symbol] = this match case Ref(sym) => S(sym) @@ -76,7 +76,7 @@ enum Term extends Statement: case SetRef(ref, value) => "mutable reference assignment" case Deref(ref) => "dereference" case Throw(e) => "throw" - case Annotated(qualifier, target) => "annotation" + case Annotated(annotation, target) => "annotation" end Term import Term.* @@ -132,7 +132,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: case Try(body, finallyDo) => body :: finallyDo :: Nil case Handle(lhs, rhs, defs) => rhs :: defs.flatMap(_.td.subTerms) case Neg(e) => e :: Nil - case Annotated(qualifier, target) => qualifier :: target :: Nil + case Annotated(annotation, target) => annotation :: target :: Nil protected def children: Ls[Located] = this match case t: Lit => t.lit.asTree :: Nil @@ -204,7 +204,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: cls.tparams.map(_.showDbg).mkStringOr(", ", "[", "]")}${ cls.paramsOpt.fold("")(_.toString)} ${cls.body}" case Import(sym, file) => s"import ${sym} from ${file}" - case Annotated(qualifier, target) => s"@${qualifier.showDbg} ${target.showDbg}" + case Annotated(annotation, target) => s"@${annotation.showDbg} ${target.showDbg}" final case class LetDecl(sym: LocalSymbol, annotations: Ls[Term]) extends Statement diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index 93d22d976..34aaeabf9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -104,7 +104,7 @@ object Parser: extension (trees: Ls[Tree]) /** Note that the innermost annotation is the leftmost. */ def annotate(tree: Tree): Tree = trees.foldLeft(tree): - case (target, qualifier) => Annotated(qualifier, target) + case (target, annotation) => Annotated(annotation, target) end Parser import Parser._ @@ -239,22 +239,22 @@ abstract class Parser( def block(allowNewlines: Bool)(using Line): Ls[Tree] = blockOf(prefixRules, Nil, allowNewlines) - def blockOf(rule: ParseRule[Tree], qualifiers: Ls[Tree], allowNewlines: Bool)(using Line): Ls[Tree] = - wrap(rule.name)(blockOfImpl(rule, qualifiers, allowNewlines)) - def blockOfImpl(rule: ParseRule[Tree], qualifiers: Ls[Tree], allowNewlines: Bool): Ls[Tree] = - def blockContOf(rule: ParseRule[Tree], qualifiers: Ls[Tree] = Nil): Ls[Tree] = + def blockOf(rule: ParseRule[Tree], annotations: Ls[Tree], allowNewlines: Bool)(using Line): Ls[Tree] = + wrap(rule.name)(blockOfImpl(rule, annotations, allowNewlines)) + def blockOfImpl(rule: ParseRule[Tree], annotations: Ls[Tree], allowNewlines: Bool): Ls[Tree] = + def blockContOf(rule: ParseRule[Tree], annotations: Ls[Tree] = Nil): Ls[Tree] = yeetSpaces match - case (COMMA, _) :: _ => consume; blockOf(rule, qualifiers, allowNewlines) - case (SEMI, _) :: _ => consume; blockOf(rule, qualifiers, allowNewlines) - case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, qualifiers, allowNewlines) + case (COMMA, _) :: _ => consume; blockOf(rule, annotations, allowNewlines) + case (SEMI, _) :: _ => consume; blockOf(rule, annotations, allowNewlines) + case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, annotations, allowNewlines) case _ => Nil cur match case Nil => Nil - case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, qualifiers, allowNewlines) - case (SPACE, _) :: _ => consume; blockOf(rule, qualifiers, allowNewlines) + case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, annotations, allowNewlines) + case (SPACE, _) :: _ => consume; blockOf(rule, annotations, allowNewlines) case (IDENT("@", _), l0) :: _ => consume - blockOf(rule, simpleExpr(AppPrec) :: qualifiers, allowNewlines) + blockOf(rule, simpleExpr(AppPrec) :: annotations, allowNewlines) case (tok @ (id: IDENT), loc) :: _ => Keyword.all.get(id.name) match case S(kw) => @@ -268,10 +268,10 @@ abstract class Parser( if blk.isEmpty then err((msg"Expected ${subRule.whatComesAfter} ${subRule.mkAfterStr}; found end of block instead" -> S(loc) :: Nil)) errExpr - blk.map(qualifiers.annotate) ::: blockContOf(rule) + blk.map(annotations.annotate) ::: blockContOf(rule) case _ => val res = parseRule(CommaPrecNext, subRule).getOrElse(errExpr) - qualifiers.annotate(exprCont(res, CommaPrecNext, false)) :: blockContOf(rule) + annotations.annotate(exprCont(res, CommaPrecNext, false)) :: blockContOf(rule) case N => // TODO dedup this common-looking logic: @@ -281,7 +281,7 @@ abstract class Parser( yeetSpaces match case (tok @ BRACKETS(Indent | Curly, toks), loc) :: _ /* if subRule.blkAlt.isEmpty */ => consume - if qualifiers.nonEmpty then + if annotations.nonEmpty then err((msg"Blocks cannot be annotated" -> S(loc) :: Nil)) prefixRules.kwAlts.get(kw.name) match case S(subRule) if subRule.blkAlt.isEmpty => @@ -294,14 +294,14 @@ abstract class Parser( prefixRules.kwAlts.get(kw.name) match case S(subRule) => val e = parseRule(CommaPrecNext, subRule).getOrElse(errExpr) - qualifiers.annotate(parseRule(CommaPrecNext, exprAlt.rest).map(res => exprAlt.k(e, res)).getOrElse(errExpr)) :: blockContOf(rule) + annotations.annotate(parseRule(CommaPrecNext, exprAlt.rest).map(res => exprAlt.k(e, res)).getOrElse(errExpr)) :: blockContOf(rule) case N => // TODO dedup? err((msg"Expected ${rule.whatComesAfter} ${rule.mkAfterStr}; found ${tok.describe} instead" -> S(loc) :: Nil)) - qualifiers.annotate(errExpr) :: blockContOf(rule) + annotations.annotate(errExpr) :: blockContOf(rule) case N => err((msg"Expected ${rule.whatComesAfter} ${rule.mkAfterStr}; found ${tok.describe} instead" -> S(loc) :: Nil)) - qualifiers.annotate(errExpr) :: blockContOf(rule) + annotations.annotate(errExpr) :: blockContOf(rule) case N => val lhs = tryParseExp(CommaPrecNext, tok, loc, rule).getOrElse(errExpr) cur match @@ -310,9 +310,9 @@ abstract class Parser( val rhs = expr(CommaPrecNext) Def(lhs, rhs) :: blockContOf(rule) case _ => - qualifiers.annotate(lhs) :: blockContOf(rule) + annotations.annotate(lhs) :: blockContOf(rule) case (tok, loc) :: _ => - qualifiers.annotate(tryParseExp(CommaPrecNext, tok, loc, rule).getOrElse(errExpr)) :: blockContOf(rule) + annotations.annotate(tryParseExp(CommaPrecNext, tok, loc, rule).getOrElse(errExpr)) :: blockContOf(rule) private def tryParseExp[A](prec: Int, tok: Token, loc: Loc, rule: ParseRule[A]): Opt[A] = @@ -479,8 +479,8 @@ abstract class Parser( yeetSpaces match case (IDENT("@", _), l0) :: _ => consume - val qualifier = simpleExpr(AppPrec) - Annotated(qualifier, simpleExpr(prec)) + val annotation = simpleExpr(AppPrec) + Annotated(annotation, simpleExpr(prec)) case (IDENT(nme, sym), loc) :: _ => Keyword.all.get(nme) match case S(kw) => // * Expressions starting with keywords should be handled in parseRule diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index ef87fe565..5d29df509 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -76,7 +76,7 @@ enum Tree extends AutoLocated: case RegRef(reg: Tree, value: Tree) case Effectful(eff: Tree, body: Tree) case Spread(kw: Keyword.Ellipsis, kwLoc: Opt[Loc], body: Opt[Tree]) - case Annotated(qualifier: Tree, target: Tree) + case Annotated(annotation: Tree, target: Tree) def children: Ls[Tree] = this match case _: Empty | _: Error | _: Ident | _: Literal | _: Under => Nil @@ -110,7 +110,7 @@ enum Tree extends AutoLocated: case Open(bod) => bod :: Nil case Def(lhs, rhs) => lhs :: rhs :: Nil case Spread(_, _, body) => body.toList - case Annotated(qualifier, target) => qualifier :: target :: Nil + case Annotated(annotation, target) => annotation :: target :: Nil def describe: Str = this match case Empty() => "empty" diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls index 1def777fb..963a940e5 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls @@ -165,41 +165,38 @@ module internal //│ ║ ^^^^^^^^ //│ ╙── Annotations are not supported on this object. +:w @volatile let @shared counter = 0 @transient @thread_local cache = () @volatile @transient empty = "" normal = () -//│ FAILURE: Unexpected warning //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.168: @volatile let +//│ ║ l.169: @volatile let //│ ║ ^^^^^^^^^^^^ -//│ ║ l.169: @shared counter = 0 +//│ ║ l.170: @shared counter = 0 //│ ║ ^^^^^^^^^ //│ ╙── Annotations are not supported on this let declaration. -//│ FAILURE: Unexpected warning //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.168: @volatile let +//│ ║ l.169: @volatile let //│ ║ ^^^^^^^^^^^^ -//│ ║ l.169: @shared counter = 0 +//│ ║ l.170: @shared counter = 0 //│ ║ ^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.170: @transient @thread_local cache = () +//│ ║ l.171: @transient @thread_local cache = () //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Annotations are not supported on this let declaration. -//│ FAILURE: Unexpected warning //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.168: @volatile let +//│ ║ l.169: @volatile let //│ ║ ^^^^^^^^^^^^ -//│ ║ l.169: @shared counter = 0 +//│ ║ l.170: @shared counter = 0 //│ ║ ^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.170: @transient @thread_local cache = () +//│ ║ l.171: @transient @thread_local cache = () //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.171: @volatile @transient empty = "" +//│ ║ l.172: @volatile @transient empty = "" //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Annotations are not supported on this let declaration. -//│ FAILURE: Unexpected warning //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.168: @volatile let +//│ ║ l.169: @volatile let //│ ║ ^^^^^^^^ //│ ╙── Annotations are not supported on this let declaration. //│ counter = 0 From be18212539ffd4a9d728d46c1fb0fc68d753bfd2 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Thu, 9 Jan 2025 12:37:48 +0800 Subject: [PATCH 11/16] Some warnings need to be reported in the elaborator --- .../src/main/scala/hkmc2/syntax/Tree.scala | 1 + .../syntax/annotations/Unsupported.mls | 46 ++++++++++++------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 5d29df509..603218928 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -148,6 +148,7 @@ enum Tree extends AutoLocated: case Def(lhs, rhs) => "defining assignment" case Spread(_, _, _) => "spread" case Annotated(_, _) => "annotated" + case Open(_) => "open" def showDbg: Str = toString // TODO diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls index c3e4f5d1e..ceb8b79d4 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls @@ -3,53 +3,65 @@ module debug +import "../../../mlscript-compile/Str.mls" + :w -@debug 0 +@debug +open Str //│ ╔══[WARNING] This annotation has no effect -//│ ║ l.7: @debug 0 +//│ ║ l.9: @debug //│ ║ ^^^^^ +//│ ╟── Annotations are not supported on open +//│ ║ l.10: open Str +//│ ╙── ^^^ + +:w +@debug 0 +//│ ╔══[WARNING] This annotation has no effect +//│ ║ l.19: @debug 0 +//│ ║ ^^^^^ //│ ╟── Annotations are not supported on integer literal -//│ ║ l.7: @debug 0 -//│ ╙── ^ +//│ ║ l.19: @debug 0 +//│ ╙── ^ //│ = 0 :w @debug 1 + 2 //│ ╔══[WARNING] This annotation has no effect -//│ ║ l.17: @debug 1 + 2 +//│ ║ l.29: @debug 1 + 2 //│ ║ ^^^^^ //│ ╟── Annotations are not supported on application -//│ ║ l.17: @debug 1 + 2 +//│ ║ l.29: @debug 1 + 2 //│ ╙── ^^^^^ //│ = 3 :w (@debug 1 + 2) //│ ╔══[WARNING] This annotation has no effect -//│ ║ l.27: (@debug 1 + 2) +//│ ║ l.39: (@debug 1 + 2) //│ ║ ^^^^^ //│ ╟── Annotations are not supported on application -//│ ║ l.27: (@debug 1 + 2) +//│ ║ l.39: (@debug 1 + 2) //│ ╙── ^^^^^ //│ = 3 :w (@debug 1) + 2 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.37: (@debug 1) + 2 +//│ ║ l.49: (@debug 1) + 2 //│ ║ ^^^^^ //│ ╟── Annotations are not supported on integer literal terms. -//│ ║ l.37: (@debug 1) + 2 +//│ ║ l.49: (@debug 1) + 2 //│ ╙── ^ //│ = 3 :w (1 + @debug 2) //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.47: (1 + @debug 2) +//│ ║ l.59: (1 + @debug 2) //│ ║ ^^^^^ //│ ╟── Annotations are not supported on integer literal terms. -//│ ║ l.47: (1 + @debug 2) +//│ ║ l.59: (1 + @debug 2) //│ ╙── ^ //│ = 3 @@ -58,10 +70,10 @@ class Log(msg: Str) :w id(@Log 5) //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.59: id(@Log 5) +//│ ║ l.71: id(@Log 5) //│ ║ ^^^ //│ ╟── Annotations are not supported on integer literal terms. -//│ ║ l.59: id(@Log 5) +//│ ║ l.71: id(@Log 5) //│ ╙── ^ //│ = 5 @@ -69,12 +81,12 @@ id(@Log 5) :w @1 + 2 class Qux //│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead -//│ ║ l.70: @1 + 2 class Qux +//│ ║ l.82: @1 + 2 class Qux //│ ╙── ^^^^^ //│ ╔══[WARNING] This annotation has no effect -//│ ║ l.70: @1 + 2 class Qux +//│ ║ l.82: @1 + 2 class Qux //│ ║ ^ //│ ╟── Annotations are not supported on application -//│ ║ l.70: @1 + 2 class Qux +//│ ║ l.82: @1 + 2 class Qux //│ ╙── ^^^ //│ = 2 From f2112de4da9b6fa1cbcf09b15a6b64cf2dfa7c96 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Thu, 9 Jan 2025 14:30:23 +0800 Subject: [PATCH 12/16] Update hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala Co-authored-by: Lionel Parreaux --- hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 85bc7860a..7d0028f3a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -392,10 +392,7 @@ class Lowering(using TL, Raise, Elaborator.State): case (acc, term) => acc match case N => term.toLoc case S(loc) => S(loc ++ term.toLoc)) :: - msg"Annotations are not supported on this ${target match - case _: LetDecl => "let declaration" - case td: TermDefinition => td.k.desc - case cls: ClassLikeDef => cls.kind.desc }." -> target.toLoc :: Nil)) + Nil)) trait LoweringSelSanityChecks From cb9e82c9b27fd892619a55bbf58445b14e30f7a7 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Thu, 9 Jan 2025 14:30:32 +0800 Subject: [PATCH 13/16] Update hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala Co-authored-by: Lionel Parreaux --- hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index c8a538125..af139696b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -539,7 +539,7 @@ extends Importer: case S(loc) => S(loc ++ ann.toLoc) ) :: (sts.headOption match case N => msg"A target term is expected at the end of block" -> blk.toLoc.map(_.right) - case S(head) => msg"Annotations are not supported on ${head.describe}" -> head.toLoc + case S(head) => msg"Annotations are not supported on ${head.describe} terms." -> head.toLoc ) :: Nil sts match case Nil => From e3a20e296513d5cce69b468f1817ff8f7d020b32 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Thu, 9 Jan 2025 14:34:08 +0800 Subject: [PATCH 14/16] Amend test changes --- .../syntax/annotations/Declarations.mls | 132 +++++++----------- .../syntax/annotations/Unsupported.mls | 10 +- 2 files changed, 53 insertions(+), 89 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls index 963a940e5..6f65dd098 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls @@ -10,10 +10,7 @@ tailrec if n == 0 then acc else fact_n(n - 1, n * acc) //│ ╔══[WARNING] This annotation has no effect. //│ ║ l.9: @tailrec fun fact_n(n, acc) = -//│ ║ ^^^^^^^ -//│ ╟── Annotations are not supported on this function. -//│ ║ l.10: if n == 0 then acc else fact_n(n - 1, n * acc) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^ :w fun fact(n) = @@ -21,37 +18,27 @@ fun fact(n) = if n == 0 then acc else go(n - 1, n * acc) go(n, 1) //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.20: @tailrec fun go(n, acc) = -//│ ║ ^^^^^^^ -//│ ╟── Annotations are not supported on this function. -//│ ║ l.21: if n == 0 then acc else go(n - 1, n * acc) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.17: @tailrec fun go(n, acc) = +//│ ╙── ^^^^^^^ class Freezed(degree: Num) :w @Freezed(-273.15) class AbsoluteZero //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.33: @Freezed(-273.15) class AbsoluteZero -//│ ║ ^^^^^^^^^^^^^^^^ -//│ ╙── Annotations are not supported on this class. +//│ ║ l.27: @Freezed(-273.15) class AbsoluteZero +//│ ╙── ^^^^^^^^^^^^^^^^ :w @Freezed(-18) class Beverage(name: Str) //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.40: @Freezed(-18) class Beverage(name: Str) -//│ ║ ^^^^^^^^^^^^ -//│ ╟── Annotations are not supported on this class. -//│ ║ l.40: @Freezed(-18) class Beverage(name: Str) -//│ ╙── ^^^ +//│ ║ l.33: @Freezed(-18) class Beverage(name: Str) +//│ ╙── ^^^^^^^^^^^^ :w @Freezed(-4) let drink = Beverage("Coke") //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.49: @Freezed(-4) let drink = Beverage("Coke") -//│ ║ ^^^^^^^^^^^ -//│ ╟── Annotations are not supported on this let declaration. -//│ ║ l.49: @Freezed(-4) let drink = Beverage("Coke") +//│ ║ l.39: @Freezed(-4) let drink = Beverage("Coke") //│ ╙── ^^^^^^^^^^^ //│ drink = Beverage { name: 'Coke' } @@ -61,23 +48,20 @@ module Foo with :w @Foo.Bar("baz") class Qux //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.62: @Foo.Bar("baz") class Qux -//│ ║ ^^^^^^^^^^^^^^ -//│ ╙── Annotations are not supported on this class. +//│ ║ l.49: @Foo.Bar("baz") class Qux +//│ ╙── ^^^^^^^^^^^^^^ :w @42 class Qux //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.69: @42 class Qux -//│ ║ ^^ -//│ ╙── Annotations are not supported on this class. +//│ ║ l.55: @42 class Qux +//│ ╙── ^^ :w @(1 + 2) class Qux //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.76: @(1 + 2) class Qux -//│ ║ ^^^^^ -//│ ╙── Annotations are not supported on this class. +//│ ║ l.61: @(1 + 2) class Qux +//│ ╙── ^^^^^ module inline @@ -88,30 +72,22 @@ fun min(x, y) = if x < y then x else y max(x, y) = if x > y then x else y //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.86: @inline -//│ ║ ^^^^^^ -//│ ╟── Annotations are not supported on this function. -//│ ║ l.88: min(x, y) = if x < y then x else y -//│ ╙── ^^^^^^^^^^^^^^^^^^^ +//│ ║ l.70: @inline +//│ ╙── ^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.86: @inline -//│ ║ ^^^^^^ -//│ ╟── Annotations are not supported on this function. -//│ ║ l.89: max(x, y) = if x > y then x else y -//│ ╙── ^^^^^^^^^^^^^^^^^^^ +//│ ║ l.70: @inline +//│ ╙── ^^^^^^ :w @inline let abs(x) = if x < 0 then -x else x clamp(x, lo, hi) = min(max(x, lo), hi) //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.104: @inline let -//│ ║ ^^^^^^ -//│ ╙── Annotations are not supported on this let declaration. +//│ ║ l.82: @inline let +//│ ╙── ^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.104: @inline let -//│ ║ ^^^^^^ -//│ ╙── Annotations are not supported on this let declaration. +//│ ║ l.82: @inline let +//│ ╙── ^^^^^^ //│ abs = [Function (anonymous)] //│ clamp = [Function (anonymous)] @@ -121,9 +97,8 @@ let @inline success = 0 failure = 1 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.121: @inline success = 0 -//│ ║ ^^^^^^ -//│ ╙── Annotations are not supported on this let declaration. +//│ ║ l.97: @inline success = 0 +//│ ╙── ^^^^^^ //│ failure = 1 //│ success = 0 @@ -134,11 +109,8 @@ let @inline @tailrec fun fib(n) = if n < 2 then n else fib(n - 1) + fib(n - 2) //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.134: @inline @tailrec fun fib(n) = -//│ ║ ^^^^^^^^^^^^^^^ -//│ ╟── Annotations are not supported on this function. -//│ ║ l.135: if n < 2 then n else fib(n - 1) + fib(n - 2) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.109: @inline @tailrec fun fib(n) = +//│ ╙── ^^^^^^^^^^^^^^^ module internal @@ -149,21 +121,17 @@ module internal transient volatile //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.146: @internal object -//│ ║ ^^^^^^^^ -//│ ╙── Annotations are not supported on this object. +//│ ║ l.118: @internal object +//│ ╙── ^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.146: @internal object -//│ ║ ^^^^^^^^ -//│ ╙── Annotations are not supported on this object. +//│ ║ l.118: @internal object +//│ ╙── ^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.146: @internal object -//│ ║ ^^^^^^^^ -//│ ╙── Annotations are not supported on this object. +//│ ║ l.118: @internal object +//│ ╙── ^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.146: @internal object -//│ ║ ^^^^^^^^ -//│ ╙── Annotations are not supported on this object. +//│ ║ l.118: @internal object +//│ ╙── ^^^^^^^^ :w @volatile let @@ -172,32 +140,28 @@ module internal @volatile @transient empty = "" normal = () //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.169: @volatile let +//│ ║ l.137: @volatile let //│ ║ ^^^^^^^^^^^^ -//│ ║ l.170: @shared counter = 0 -//│ ║ ^^^^^^^^^ -//│ ╙── Annotations are not supported on this let declaration. +//│ ║ l.138: @shared counter = 0 +//│ ╙── ^^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.169: @volatile let +//│ ║ l.137: @volatile let //│ ║ ^^^^^^^^^^^^ -//│ ║ l.170: @shared counter = 0 +//│ ║ l.138: @shared counter = 0 //│ ║ ^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.171: @transient @thread_local cache = () -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╙── Annotations are not supported on this let declaration. +//│ ║ l.139: @transient @thread_local cache = () +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.169: @volatile let +//│ ║ l.137: @volatile let //│ ║ ^^^^^^^^^^^^ -//│ ║ l.170: @shared counter = 0 +//│ ║ l.138: @shared counter = 0 //│ ║ ^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.171: @transient @thread_local cache = () +//│ ║ l.139: @transient @thread_local cache = () //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.172: @volatile @transient empty = "" -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ -//│ ╙── Annotations are not supported on this let declaration. +//│ ║ l.140: @volatile @transient empty = "" +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.169: @volatile let -//│ ║ ^^^^^^^^ -//│ ╙── Annotations are not supported on this let declaration. +//│ ║ l.137: @volatile let +//│ ╙── ^^^^^^^^ //│ counter = 0 //│ empty = '' diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls index ceb8b79d4..439f00a3e 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls @@ -11,7 +11,7 @@ open Str //│ ╔══[WARNING] This annotation has no effect //│ ║ l.9: @debug //│ ║ ^^^^^ -//│ ╟── Annotations are not supported on open +//│ ╟── Annotations are not supported on open terms. //│ ║ l.10: open Str //│ ╙── ^^^ @@ -20,7 +20,7 @@ open Str //│ ╔══[WARNING] This annotation has no effect //│ ║ l.19: @debug 0 //│ ║ ^^^^^ -//│ ╟── Annotations are not supported on integer literal +//│ ╟── Annotations are not supported on integer literal terms. //│ ║ l.19: @debug 0 //│ ╙── ^ //│ = 0 @@ -30,7 +30,7 @@ open Str //│ ╔══[WARNING] This annotation has no effect //│ ║ l.29: @debug 1 + 2 //│ ║ ^^^^^ -//│ ╟── Annotations are not supported on application +//│ ╟── Annotations are not supported on application terms. //│ ║ l.29: @debug 1 + 2 //│ ╙── ^^^^^ //│ = 3 @@ -40,7 +40,7 @@ open Str //│ ╔══[WARNING] This annotation has no effect //│ ║ l.39: (@debug 1 + 2) //│ ║ ^^^^^ -//│ ╟── Annotations are not supported on application +//│ ╟── Annotations are not supported on application terms. //│ ║ l.39: (@debug 1 + 2) //│ ╙── ^^^^^ //│ = 3 @@ -86,7 +86,7 @@ id(@Log 5) //│ ╔══[WARNING] This annotation has no effect //│ ║ l.82: @1 + 2 class Qux //│ ║ ^ -//│ ╟── Annotations are not supported on application +//│ ╟── Annotations are not supported on application terms. //│ ║ l.82: @1 + 2 class Qux //│ ╙── ^^^ //│ = 2 From 82918bc14c2adf9f0564c1c972fba2a13a653581 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Thu, 9 Jan 2025 14:55:10 +0800 Subject: [PATCH 15/16] Add an edge test case --- .../test/mlscript/syntax/annotations/Unsupported.mls | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls index 439f00a3e..45a9a3ba7 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls @@ -90,3 +90,13 @@ id(@Log 5) //│ ║ l.82: @1 + 2 class Qux //│ ╙── ^^^ //│ = 2 + +:todo +class Foo with + @debug + constructor + Bar(qax: Str) +//│ ╔══[PARSE ERROR] Blocks cannot be annotated +//│ ║ l.98: Bar(qax: Str) +//│ ╙── ^^^^ +//│ /!!!\ Uncaught error: scala.NotImplementedError: List() (of class Nil$) From e907ad68197d93d63b00bb53fadac7dbed0c0522 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Thu, 9 Jan 2025 16:01:45 +0800 Subject: [PATCH 16/16] Pass on annotations --- hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala | 4 +--- .../test/mlscript/syntax/annotations/Unsupported.mls | 10 ---------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index 34aaeabf9..aa5429ac9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -281,12 +281,10 @@ abstract class Parser( yeetSpaces match case (tok @ BRACKETS(Indent | Curly, toks), loc) :: _ /* if subRule.blkAlt.isEmpty */ => consume - if annotations.nonEmpty then - err((msg"Blocks cannot be annotated" -> S(loc) :: Nil)) prefixRules.kwAlts.get(kw.name) match case S(subRule) if subRule.blkAlt.isEmpty => rec(toks, S(tok.innerLoc), tok.describe).concludeWith { p => - p.blockOf(subRule.map(e => parseRule(CommaPrecNext, exprAlt.rest).map(res => exprAlt.k(e, res)).getOrElse(errExpr)), Nil, allowNewlines) + p.blockOf(subRule.map(e => parseRule(CommaPrecNext, exprAlt.rest).map(res => exprAlt.k(e, res)).getOrElse(errExpr)), annotations, allowNewlines) } ++ blockContOf(rule) case _ => TODO(cur) diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls index 45a9a3ba7..439f00a3e 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls @@ -90,13 +90,3 @@ id(@Log 5) //│ ║ l.82: @1 + 2 class Qux //│ ╙── ^^^ //│ = 2 - -:todo -class Foo with - @debug - constructor - Bar(qax: Str) -//│ ╔══[PARSE ERROR] Blocks cannot be annotated -//│ ║ l.98: Bar(qax: Str) -//│ ╙── ^^^^ -//│ /!!!\ Uncaught error: scala.NotImplementedError: List() (of class Nil$)