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 35507bc49..7d0028f3a 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 @@ -148,7 +152,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 => @@ -339,6 +344,12 @@ class Lowering(using TL, Raise, Elaborator.State): subTerm(rhs): value => AssignField(ref, Tree.Ident("value"), value, k(value))(N) + case Annotated(prefix, receiver) => + 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") // case _ => @@ -374,6 +385,14 @@ 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)) :: + Nil)) trait LoweringSelSanityChecks 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 cfaae8e3f..af139696b 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)))) @@ -467,6 +467,13 @@ extends Importer: case Under() => raise(ErrorReport(msg"Illegal position for '_' placeholder." -> tree.toLoc :: Nil)) Term.Error + case Annotated(lhs, rhs) => + val annotation = lhs match + case App(_: (Ident | SynthSel | Sel), _) | _: (Ident | SynthSel | Sel) => term(lhs) + case _ => + raise(ErrorReport(msg"Illegal annotation shape." -> lhs.toLoc :: Nil)) + Term.Error + Term.Annotated(annotation, term(rhs)) // case _ => // ??? @@ -522,11 +529,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)] = + /** Call this function when the following term cannot be annotated. */ + def reportUnusedAnnotations: Unit = if annotations.nonEmpty then raise: + WarningReport: + 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) + ) :: (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} terms." -> 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)) @@ -537,7 +558,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 => @@ -561,14 +582,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) @@ -580,7 +602,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 = @@ -590,17 +612,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}") @@ -611,10 +634,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)) @@ -627,29 +650,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 @@ -674,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)) + TermDefFlags.empty.copy(isModMember = isModMember), annotations) sym.defn = S(tdf) // indicates if the function really returns a module @@ -705,10 +729,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 @@ -759,7 +784,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 => @@ -770,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))))) + 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) => @@ -784,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)) + ModuleDef(owner, clsSym, tps, ps, k, ObjBody(bod), annotations) clsSym.defn = S(cd) cd case Cls => @@ -798,28 +823,32 @@ 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(annotation, target) :: sts => + go(target :: sts, annotations :+ term(annotation), 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 = @@ -902,7 +931,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 77bebe7dd..4b21ee1b5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -40,6 +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(annotation: Term, target: Term) lazy val symbol: Opt[Symbol] = this match case Ref(sym) => S(sym) @@ -75,6 +76,7 @@ enum Term extends Statement: case SetRef(ref, value) => "mutable reference assignment" case Deref(ref) => "dereference" case Throw(e) => "throw" + case Annotated(annotation, target) => "annotation" end Term import Term.* @@ -109,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 => @@ -130,6 +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(annotation, target) => annotation :: target :: Nil protected def children: Ls[Located] = this match case t: Lit => t.lit.asTree :: Nil @@ -178,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}" @@ -189,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 @@ -201,8 +204,9 @@ 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(annotation, target) => s"@${annotation.showDbg} ${target.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 @@ -224,6 +228,7 @@ final case class TermDefinition( body: Opt[Term], resSym: FlowSymbol, flags: TermDefFlags, + annotations: Ls[Term], ) extends Companion final case class HandlerTermDefinition( @@ -249,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] @@ -257,6 +263,7 @@ sealed abstract class ClassLikeDef extends TypeLikeDef: val tparams: Ls[TyParam] val kind: ClsLikeKind val body: ObjBody + val annotations: Ls[Term] case class ModuleDef( @@ -266,6 +273,7 @@ case class ModuleDef( paramsOpt: Opt[ParamList], kind: ClsLikeKind, body: ObjBody, + annotations: Ls[Term], ) extends ClassLikeDef with Companion case class PatternDef( @@ -273,7 +281,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 @@ -286,14 +295,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)) @@ -302,7 +312,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) @@ -310,7 +321,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 @@ -321,7 +333,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/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index 58041b863..aa5429ac9 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 (target, annotation) => Annotated(annotation, target) + 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], 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, allowNewlines) - case (SEMI, _) :: _ => consume; blockOf(rule, allowNewlines) - case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, 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, allowNewlines) - case (SPACE, _) :: _ => consume; blockOf(rule, 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) :: annotations, 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.map(annotations.annotate) ::: blockContOf(rule) case _ => val res = parseRule(CommaPrecNext, subRule).getOrElse(errExpr) - exprCont(res, CommaPrecNext, false) :: blockContOf(rule) + annotations.annotate(exprCont(res, CommaPrecNext, false)) :: blockContOf(rule) case N => // TODO dedup this common-looking logic: @@ -276,7 +284,7 @@ abstract class Parser( 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)), annotations, allowNewlines) } ++ blockContOf(rule) case _ => TODO(cur) @@ -284,15 +292,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) + 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)) - errExpr :: blockContOf(rule) + annotations.annotate(errExpr) :: blockContOf(rule) case N => err((msg"Expected ${rule.whatComesAfter} ${rule.mkAfterStr}; found ${tok.describe} instead" -> S(loc) :: Nil)) - errExpr :: blockContOf(rule) - + annotations.annotate(errExpr) :: blockContOf(rule) case N => val lhs = tryParseExp(CommaPrecNext, tok, loc, rule).getOrElse(errExpr) cur match @@ -301,9 +308,9 @@ abstract class Parser( val rhs = expr(CommaPrecNext) Def(lhs, rhs) :: blockContOf(rule) case _ => - lhs :: blockContOf(rule) + annotations.annotate(lhs) :: blockContOf(rule) case (tok, loc) :: _ => - 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] = @@ -468,6 +475,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 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 488023faf..603218928 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -76,6 +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(annotation: Tree, target: Tree) def children: Ls[Tree] = this match case _: Empty | _: Error | _: Ident | _: Literal | _: Under => Nil @@ -109,6 +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(annotation, target) => annotation :: target :: Nil def describe: Str = this match case Empty() => "empty" @@ -145,6 +147,8 @@ enum Tree extends AutoLocated: case Handle(_, _, _, _) => "handle" case Def(lhs, rhs) => "defining assignment" case Spread(_, _, _) => "spread" + case Annotated(_, _) => "annotated" + case Open(_) => "open" def showDbg: Str = toString // TODO @@ -202,6 +206,11 @@ object Apps: def unapply(t: Tree): S[(Tree, Ls[Tup])] = t match case App(Apps(base, args), arg: Tup) => S(base, args :+ arg) case t => S(t, Nil) + +object Annotations: + def unapply(t: Tree): Opt[(Ls[Tree], Tree)] = t match + 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. */ object PartialApp: @@ -230,7 +239,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/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/Declarations.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls new file mode 100644 index 000000000..6f65dd098 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls @@ -0,0 +1,167 @@ +:js + +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) = +//│ ╙── ^^^^^^^ + +: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.17: @tailrec fun go(n, acc) = +//│ ╙── ^^^^^^^ + +class Freezed(degree: Num) + +:w +@Freezed(-273.15) class AbsoluteZero +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.27: @Freezed(-273.15) class AbsoluteZero +//│ ╙── ^^^^^^^^^^^^^^^^ + +:w +@Freezed(-18) class Beverage(name: Str) +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.33: @Freezed(-18) class Beverage(name: Str) +//│ ╙── ^^^^^^^^^^^^ + +:w +@Freezed(-4) let drink = Beverage("Coke") +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.39: @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.49: @Foo.Bar("baz") class Qux +//│ ╙── ^^^^^^^^^^^^^^ + +:w +@42 class Qux +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.55: @42 class Qux +//│ ╙── ^^ + +:w +@(1 + 2) class Qux +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.61: @(1 + 2) class Qux +//│ ╙── ^^^^^ + +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.70: @inline +//│ ╙── ^^^^^^ +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ 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.82: @inline let +//│ ╙── ^^^^^^ +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.82: @inline let +//│ ╙── ^^^^^^ +//│ 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.97: @inline success = 0 +//│ ╙── ^^^^^^ +//│ 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.109: @inline @tailrec fun fib(n) = +//│ ╙── ^^^^^^^^^^^^^^^ + +module internal + +:w +@internal object + shared + thread_local + transient + volatile +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.118: @internal object +//│ ╙── ^^^^^^^^ +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.118: @internal object +//│ ╙── ^^^^^^^^ +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.118: @internal object +//│ ╙── ^^^^^^^^ +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.118: @internal object +//│ ╙── ^^^^^^^^ + +:w +@volatile let + @shared counter = 0 + @transient @thread_local cache = () + @volatile @transient empty = "" + normal = () +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.137: @volatile let +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.138: @shared counter = 0 +//│ ╙── ^^^^^^^^^ +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.137: @volatile let +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.138: @shared counter = 0 +//│ ║ ^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.139: @transient @thread_local cache = () +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.137: @volatile let +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.138: @shared counter = 0 +//│ ║ ^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.139: @transient @thread_local cache = () +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.140: @volatile @transient empty = "" +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.137: @volatile let +//│ ╙── ^^^^^^^^ +//│ counter = 0 +//│ empty = '' 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/Unsupported.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls new file mode 100644 index 000000000..439f00a3e --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls @@ -0,0 +1,92 @@ +:js +// The annotations for the following expressions are not supported for now. + +module debug + +import "../../../mlscript-compile/Str.mls" + +:w +@debug +open Str +//│ ╔══[WARNING] This annotation has no effect +//│ ║ l.9: @debug +//│ ║ ^^^^^ +//│ ╟── Annotations are not supported on open terms. +//│ ║ l.10: open Str +//│ ╙── ^^^ + +:w +@debug 0 +//│ ╔══[WARNING] This annotation has no effect +//│ ║ l.19: @debug 0 +//│ ║ ^^^^^ +//│ ╟── Annotations are not supported on integer literal terms. +//│ ║ l.19: @debug 0 +//│ ╙── ^ +//│ = 0 + +:w +@debug 1 + 2 +//│ ╔══[WARNING] This annotation has no effect +//│ ║ l.29: @debug 1 + 2 +//│ ║ ^^^^^ +//│ ╟── Annotations are not supported on application terms. +//│ ║ l.29: @debug 1 + 2 +//│ ╙── ^^^^^ +//│ = 3 + +:w +(@debug 1 + 2) +//│ ╔══[WARNING] This annotation has no effect +//│ ║ l.39: (@debug 1 + 2) +//│ ║ ^^^^^ +//│ ╟── Annotations are not supported on application terms. +//│ ║ l.39: (@debug 1 + 2) +//│ ╙── ^^^^^ +//│ = 3 + +:w +(@debug 1) + 2 +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.49: (@debug 1) + 2 +//│ ║ ^^^^^ +//│ ╟── Annotations are not supported on integer literal terms. +//│ ║ l.49: (@debug 1) + 2 +//│ ╙── ^ +//│ = 3 + +:w +(1 + @debug 2) +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.59: (1 + @debug 2) +//│ ║ ^^^^^ +//│ ╟── Annotations are not supported on integer literal terms. +//│ ║ l.59: (1 + @debug 2) +//│ ╙── ^ +//│ = 3 + +class Log(msg: Str) + +:w +id(@Log 5) +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.71: id(@Log 5) +//│ ║ ^^^ +//│ ╟── Annotations are not supported on integer literal terms. +//│ ║ l.71: id(@Log 5) +//│ ╙── ^ +//│ = 5 + +:pe +:w +@1 + 2 class Qux +//│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead +//│ ║ l.82: @1 + 2 class Qux +//│ ╙── ^^^^^ +//│ ╔══[WARNING] This annotation has no effect +//│ ║ l.82: @1 + 2 class Qux +//│ ║ ^ +//│ ╟── Annotations are not supported on application terms. +//│ ║ l.82: @1 + 2 class Qux +//│ ╙── ^^^ +//│ = 2 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