From 75471280358ebd6a6c1622bf6ea62d2c51bc49bc Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 16 Sep 2026 10:05:56 -0400 Subject: [PATCH 01/23] Infer every match arm in the checker. Later match arms were not inferred. The checker now infers each arm and requires every arm body type to equal the first arm body type. A mismatch reports the arm body span. Errors inside later arms, such as unbound variables, now surface. The stricter pass exposed a latent rejection: unary minus only accepted Int. Codegen already emitted fneg for Float. The checker now accepts Int and Float operands for unary minus. Bit-not keeps the Int-only rule. The tyck oracle covers multi-arm match inference, arm type mismatch, later-arm unbound variables, and unary minus on Int, Float, and Bool. LLVM IR fixed-point converges. The kernel suite passes with the new compiler. --- docs/gaps.md | 2 +- examples/compiler/src/Check.scuzz | 18 ++++++++++++++++-- examples/tyck/src/Main.scuzz | 22 ++++++++++++++++++++-- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/docs/gaps.md b/docs/gaps.md index ba3087e9..f87f1735 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -37,7 +37,7 @@ Do not add library publishing, git or registry deps, or `scuzz add`. Path deps s Resolve these gaps when they prevent ordinary language use. -1. **Checker and emit residuals** — `Map.empty`, `Set.empty`, and `List.empty` still use a bare constructor. A Queue or Deferred handle has no payload until the first offer or complete. Param letters (`A`/`E`) still unify. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Literal checks inside nested constructor patterns remain open. Match arms after the first are not inferred. Direct constructor fields compare String, Int, and Bool literals. +1. **Checker and emit residuals** — `Map.empty`, `Set.empty`, and `List.empty` still use a bare constructor. A Queue or Deferred handle has no payload until the first offer or complete. Param letters (`A`/`E`) still unify. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Literal checks inside nested constructor patterns remain open. Direct constructor fields compare String, Int, and Bool literals. 2. **Compile-time performance** — `scuzz check examples/compiler` is 16 s. A cold `scuzz build examples/tyck` is 1 m 10 s. Remaining cost: RC retain/release churn and `sz_list_concat` in string building. Coverage still parses a compiled graph that differs from live. diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index 4fecaa0b..4e7360e0 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -730,7 +730,10 @@ def inferUn(op: String, inner: Expr, env: List[(String, Ty)], funs: Ftab, ens: L inferUn2(op, infer(inner, env, funs, ens), env, e) def inferUn2(op: String, o: Out, env: List[(String, Ty)], e: Expr): Out = - if (hasErr(o)) o else if (op == "!") inferUnBool(o, env, e) else inferUnInt(o, env, e) + if (hasErr(o)) o else if (op == "!") inferUnBool(o, env, e) else if (op == "-") inferUnNum(o, env, e) else inferUnInt(o, env, e) + +def inferUnNum(o: Out, env: List[(String, Ty)], e: Expr): Out = + if (tyStr(o) == "Int" || tyStr(o) == "Float" || isLoose(tyStr(o))) o else bad(Str.concat("expected Int or Float, got ", tyStr(o)), exprSpan(env, e)) def inferUnBool(o: Out, env: List[(String, Ty)], e: Expr): Out = if (tyStr(o) == "Bool" || isLoose(tyStr(o))) ok("Bool") else bad(Str.concat("expected Bool, got ", tyStr(o)), exprSpan(env, e)) @@ -1024,7 +1027,18 @@ def inferMatch(s: Expr, arms: List[Arm], env: List[(String, Ty)], funs: Ftab, en inferMatch2(infer(s, env, funs, ens), arms, env, funs, ens, span) def inferMatch2(o: Out, arms: List[Arm], env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = - if (hasErr(o)) o else if (List.isEmpty(arms)) o else inferMatchChecked(altArmsErr(arms, tyStr(o), ens, span), List.at(arms, 0), tyStr(o), env, funs, ens) + if (hasErr(o)) o else if (List.isEmpty(arms)) o else inferMatchRest(inferMatchChecked(altArmsErr(arms, tyStr(o), ens, span), List.at(arms, 0), tyStr(o), env, funs, ens), List.tail(arms), tyStr(o), env, funs, ens) + +def inferMatchRest(first: Out, rest: List[Arm], scrutTy: String, env: List[(String, Ty)], funs: Ftab, ens: List[En]): Out = + if (hasErr(first)) first else if (List.isEmpty(rest)) first else inferMatchArm2(first, inferMatchArm(List.at(rest, 0), scrutTy, env, funs, ens), List.at(rest, 0), List.tail(rest), scrutTy, env, funs, ens) + +def inferMatchArm2(first: Out, cur: Out, arm: Arm, rest: List[Arm], scrutTy: String, env: List[(String, Ty)], funs: Ftab, ens: List[En]): Out = + if (hasErr(cur)) cur else if (tyEqEn(tyStr(first), tyStr(cur), ens)) inferMatchRest(first, rest, scrutTy, env, funs, ens) else bad(Str.concat("type mismatch: expected ", Str.concat(tyStr(first), Str.concat(", got ", tyStr(cur)))), armSpan(arm, env)) + +def armSpan(arm: Arm, env: List[(String, Ty)]): (String, Int) = + arm match { + case Arm(_, _, body) => exprSpan(env, body) + } def inferMatchChecked(v: Out, arm: Arm, scrutTy: String, env: List[(String, Ty)], funs: Ftab, ens: List[En]): Out = if (hasErr(v)) v else inferMatchArm(arm, scrutTy, env, funs, ens) diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index b2e415d0..9065df4c 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -338,7 +338,7 @@ def tyckCore(): Bool = sourceOffsets() && funLookup() && tyckDiff(srcOk(), wantOk()) && tyckDiff(srcUnbound(), wantUnbound()) && tyckDiff(srcArith(), wantArith()) && tyckDiff(srcArity(), wantArity()) && tyckDiff(srcRet(), wantRet()) && tyckDiff(srcPrint(), wantPrint()) && tyckDiff(srcUnk(), wantUnk()) && tyckDiff(srcCmp(), wantCmp()) && tyckDiff(srcMainTy(), wantMainTy()) && tyckDiff(srcArg(), wantArg()) && tyckDiff(srcWatch(), wantWatch()) && tyckDiff(srcDup(), wantDup()) && tyckFiles() def tyckFlow(): Bool = - tyckDiff(srcUnBool(), wantUnBool()) && tyckDiff(srcIfMis(), wantIfMis()) && tyckDiff(srcMapHandle(), wantMapHandle()) && tyckDiff(srcJoinMis(), wantJoinMis()) && tyckDiff(srcNope(), wantNope()) && tyckDiff(srcStrUnk(), wantStrUnk()) && tyckDiff(srcStrConcatTy(), wantStrConcatTy()) && tyckDiff(srcSysExit(), wantSysExit()) + tyckDiff(srcUnBool(), wantUnBool()) && tyckDiff(srcIfMis(), wantIfMis()) && tyckDiff(srcMapHandle(), wantMapHandle()) && tyckDiff(srcJoinMis(), wantJoinMis()) && tyckDiff(srcNope(), wantNope()) && tyckDiff(srcStrUnk(), wantStrUnk()) && tyckDiff(srcStrConcatTy(), wantStrConcatTy()) && tyckDiff(srcSysExit(), wantSysExit()) && unaryNumTypes() def tyckRes(): Bool = tyckDiff(srcResArity(), wantResArity()) && tyckDiff(srcResDraw(), wantResDraw()) && tyckDiff(srcResLam(), wantResLam()) && tyckDiff(srcResUnk(), wantResUnk()) && tyckDiff(srcJsonUnk(), wantJsonUnk()) && tyckDiff(srcDefUnk(), wantDefUnk()) && tyckDiff(srcDefMis(), wantDefMis()) && tyckDiff(srcQueUnk(), wantQueUnk()) && tyckDiff(srcQueMis(), wantQueMis()) && tyckDiff(srcQueTake(), wantQueTake()) && tyckDiff(srcQuePay(), wantQuePay()) && tyckDiff(srcNetStr(), wantNetStr()) @@ -630,7 +630,7 @@ def srcPhOk(): String = """ def tyckAdt(): Bool = - Type.eqStr("Either[Int]", "Either[Int, Int]") && Type.eqStr("Either[String]", "Either[Int, String]") && !Type.eqStr("Either[String]", "Either[Int, Int]") && Check.check(srcEitherOk()) == "[]" && rejects(srcEitherMis(), "eitherNum arg type mismatch") && Check.check(srcShowOk()) == "[]" && Check.check(srcSelfGet()) == "[]" && Check.check(srcPhOk()) == "[]" && tyckRecordBind() && namedLetterTypes() && aliasTypes() + Type.eqStr("Either[Int]", "Either[Int, Int]") && Type.eqStr("Either[String]", "Either[Int, String]") && !Type.eqStr("Either[String]", "Either[Int, Int]") && Check.check(srcEitherOk()) == "[]" && rejects(srcEitherMis(), "eitherNum arg type mismatch") && Check.check(srcShowOk()) == "[]" && Check.check(srcSelfGet()) == "[]" && Check.check(srcPhOk()) == "[]" && tyckRecordBind() && namedLetterTypes() && aliasTypes() && matchArmTypes() def namedLetterTypes(): Bool = Check.check("""enum E: @@ -1017,6 +1017,24 @@ def value(r: Result[Problem, Int]): Int = r match { case Result.Ok(s) => Str.len(s) }""", "type mismatch") +def unaryNumTypes(): Bool = + Check.check("def right(): Float = -1.5") == "[]" && Check.check("def right(n: Float): Float = -n") == "[]" && Check.check("def right(): Int = -1") == "[]" && rejects("def wrong(): Bool = -true", "expected Int or Float, got Bool") + +def matchArmTypes(): Bool = + Check.check("""enum E: + case A + case B +def right(e: E): Int = e match { + case E.A => 1 + case E.B => 2 +}""") == "[]" && rejects("enum E:\n case A\n case B\ndef wrong(e: E): Int = e match {\n case E.A => 1\n case E.B => \"x\"\n}", "type mismatch: expected Int, got String") && rejects("""enum E: + case A + case B +def wrong(e: E): Int = e match { + case E.A => 1 + case E.B => nope +}""", "unbound variable nope") + def funLookup(): Bool = funLookupDefs(Parse.parseFiles(("A", """def pick(n: Int): Int = n def skip(): Int = 0 From c33ea915aac73031e36194e071ce0a1ac5965097 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 16 Sep 2026 10:31:45 -0400 Subject: [PATCH 02/23] Test literal components in tuple patterns. Tuple patterns never compared literal components. (1, s) matched any first component and silently took the first arm. The tuple try phase now tests Int, Bool, and String literal components against the peeled values. A mismatch branches to the next arm. Nested tuple patterns recurse with their component patterns. The checker no longer binds literal components as variables. Kernel oracles prove flat, three-component, and nested tuple literal matches, including fall-through to later arms. The concrete oracle failed before this change and passes after it. LLVM IR fixed-point converges. The kernel suite passes with the new compiler. --- docs/gaps.md | 2 +- examples/compiler/src/Check.scuzz | 2 +- examples/compiler/src/Emit.scuzz | 59 +++++++++++++++++++++++------- examples/kernel/facts.scuzz_verify | 18 +++++++++ examples/kernel/src/Main.scuzz | 34 +++++++++++++++++ 5 files changed, 100 insertions(+), 15 deletions(-) diff --git a/docs/gaps.md b/docs/gaps.md index f87f1735..b26b8be0 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -37,7 +37,7 @@ Do not add library publishing, git or registry deps, or `scuzz add`. Path deps s Resolve these gaps when they prevent ordinary language use. -1. **Checker and emit residuals** — `Map.empty`, `Set.empty`, and `List.empty` still use a bare constructor. A Queue or Deferred handle has no payload until the first offer or complete. Param letters (`A`/`E`) still unify. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Literal checks inside nested constructor patterns remain open. Direct constructor fields compare String, Int, and Bool literals. +1. **Checker and emit residuals** — `Map.empty`, `Set.empty`, and `List.empty` still use a bare constructor. A Queue or Deferred handle has no payload until the first offer or complete. Param letters (`A`/`E`) still unify. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest: a constructor pattern inside a tuple or constructor field fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. 2. **Compile-time performance** — `scuzz check examples/compiler` is 16 s. A cold `scuzz build examples/tyck` is 1 m 10 s. Remaining cost: RC retain/release churn and `sz_list_concat` in string building. Coverage still parses a compiled graph that differs from live. diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index 4e7360e0..12d488bf 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -1234,7 +1234,7 @@ def bindTuple2(ns: List[String], ts: List[String], env: List[(String, Ty)]): Lis if (List.isEmpty(ns)) env else bindSlot(List.at(ns, 0), if (List.isEmpty(ts)) "A" else List.at(ts, 0), bindTuple2(List.tail(ns), if (List.isEmpty(ts)) ts else List.tail(ts), env)) def bindSlot(n: String, ty: String, env: List[(String, Ty)]): List[(String, Ty)] = - if (isTuplePat(n)) bindTuple(n, ty, env) else envBind(n, ty, env) + if (isTuplePat(n)) bindTuple(n, ty, env) else if (altStrPat(n) || altIntPat(n) || altBoolPat(n)) env else envBind(n, ty, env) def bindTy(pat: String, scrutTy: String, ens: List[En]): String = bindTyGot(patCore(pat), fieldTy(specializeEnums(ens, scrutTy), patCore(pat)), scrutTy) diff --git a/examples/compiler/src/Emit.scuzz b/examples/compiler/src/Emit.scuzz index 9b11457a..2281d717 100644 --- a/examples/compiler/src/Emit.scuzz +++ b/examples/compiler/src/Emit.scuzz @@ -3875,7 +3875,7 @@ def emitTryPat(pat: String, prefix: String, i: Int, scrut: String, ens: List[En] if (isAsPat(pat)) emitTryPat(stripAs(pat), prefix, i, scrut, ens, strs, last, tys) else emitTryPat2(pat, prefix, i, scrut, ens, strs, last, tys) def emitTryPat2(pat: String, prefix: String, i: Int, scrut: String, ens: List[En], strs: List[String], last: Bool, tys: List[String]): String = - if (pat == "_") line(Str.concat("br label %", Str.concat(prefix, Str.concat("_ok_0_", Str.fromInt(i))))) else if (pat == "[]") emitNilTry(prefix, i, scrut, last) else if (isConsPat(pat)) emitConsTry(prefix, i, scrut, last, slotInt(tys, 0), consTail(pat)) else if (isOrPat(pat)) emitOrTry(pat, prefix, i, scrut, ens, strs, last) else if (isStrPat(pat)) emitStrTry(pat, prefix, i, scrut, strs, last) else if (isIntPat(pat)) emitIntTry(pat, prefix, i, scrut, last) else if (isBoolPat(pat)) emitIntTry(boolPatLit(pat), prefix, i, scrut, last) else if (isTuplePat(pat)) emitTupleTry(prefix, i, scrut, tys) else if (isVarPat(pat, ens)) line(Str.concat("br label %", Str.concat(prefix, Str.concat("_ok_0_", Str.fromInt(i))))) else if (isConsPat(patBind(pat))) emitCtorConsTry(pat, prefix, i, scrut, ens, last, tys) else join(emitTag(prefix, i, scrut, patTag(pat, ens)), join(emitTagBr(prefix, i, last), emitLiteralMatchedPat(pat, prefix, i, scrut, ens, strs, last))) + if (pat == "_") line(Str.concat("br label %", Str.concat(prefix, Str.concat("_ok_0_", Str.fromInt(i))))) else if (pat == "[]") emitNilTry(prefix, i, scrut, last) else if (isConsPat(pat)) emitConsTry(prefix, i, scrut, last, slotInt(tys, 0), consTail(pat)) else if (isOrPat(pat)) emitOrTry(pat, prefix, i, scrut, ens, strs, last) else if (isStrPat(pat)) emitStrTry(pat, prefix, i, scrut, strs, last) else if (isIntPat(pat)) emitIntTry(pat, prefix, i, scrut, last) else if (isBoolPat(pat)) emitIntTry(boolPatLit(pat), prefix, i, scrut, last) else if (isTuplePat(pat)) emitTupleTry(pat, prefix, i, scrut, strs, last, tys) else if (isVarPat(pat, ens)) line(Str.concat("br label %", Str.concat(prefix, Str.concat("_ok_0_", Str.fromInt(i))))) else if (isConsPat(patBind(pat))) emitCtorConsTry(pat, prefix, i, scrut, ens, last, tys) else join(emitTag(prefix, i, scrut, patTag(pat, ens)), join(emitTagBr(prefix, i, last), emitLiteralMatchedPat(pat, prefix, i, scrut, ens, strs, last))) def emitCtorConsTry(pat: String, prefix: String, i: Int, scrut: String, ens: List[En], last: Bool, tys: List[String]): String = join(emitTag(prefix, i, scrut, patTag(pat, ens)), join(emitTagBr(prefix, i, last), emitCtorConsM(pat, prefix, i, scrut, last, tys))) @@ -3925,23 +3925,56 @@ def emitConsOk(prefix: String, i: Int): String = def isTuplePat(pat: String): Bool = Str.len(pat) > 0 && Str.charAt(pat, 0) == 40 -def emitTupleTry(prefix: String, i: Int, scrut: String, tys: List[String]): String = - emitTuplePeel(ppre(prefix, i), i + 1, scrut, tys, prefix, i, true) +def emitTupleTry(pat: String, prefix: String, i: Int, scrut: String, strs: List[String], last: Bool, tys: List[String]): String = + emitTuplePeel(ppre(prefix, i), i + 1, scrut, tys, tupComps(pat), prefix, i, last, strs, true) -def emitTuplePeel(pre: String, sid: Int, scrut: String, tys: List[String], armPrefix: String, armI: Int, fin: Bool): String = - join(peelLeft(pre, sid, scrut), join(peelRight(pre, sid, scrut), join(peelBrTn(pre, sid), join(peelTnLab(pre, sid), emitTupleCont(pre, sid, tys, armPrefix, armI, fin))))) +def tupComps(pat: String): List[String] = + splitPat(Str.slice(pat, 1, Str.len(pat) - 1)) -def emitTupleCont(pre: String, sid: Int, tys: List[String], armPrefix: String, armI: Int, fin: Bool): String = - join(if (slotInt(tys, 0)) peelUnboxL(pre, sid) else "", emitTupleRest(pre, sid, tys, armPrefix, armI, fin)) +def compAt(comps: List[String], k: Int): String = + if (List.isEmpty(comps)) "" else if (k <= 0) List.at(comps, 0) else compAt(List.tail(comps), k - 1) -def emitTupleRest(pre: String, sid: Int, tys: List[String], armPrefix: String, armI: Int, fin: Bool): String = - join(emitNest(pre, sid, "tl", headTy(tys), armPrefix, armI), emitTupleRest2(pre, sid, tys, armPrefix, armI, fin)) +def nestComps(comp: String): List[String] = + if (isTuplePat(comp)) tupComps(comp) else noStr() -def emitTupleRest2(pre: String, sid: Int, tys: List[String], armPrefix: String, armI: Int, fin: Bool): String = - if (List.len(tys) > 2) emitTuplePeel(Str.concat(pre, Str.concat("_n", Str.fromInt(sid))), sid + 1, pct(pre, Str.concat("tr", Str.fromInt(sid))), List.tail(tys), armPrefix, armI, fin) else join(emitNest(pre, sid, "tr", secondTy(tys), armPrefix, armI), join(if (slotInt(tys, 1)) peelUnboxR(pre, sid) else "", if (fin) tupleBrOk(armPrefix, armI) else "")) +def tailComps(comps: List[String]): List[String] = + if (List.isEmpty(comps)) comps else List.tail(comps) -def emitNest(pre: String, sid: Int, side: String, ty: String, armPrefix: String, armI: Int): String = - if (!isTupTy(ty)) "" else emitTuplePeel(Str.concat(pre, Str.concat(Str.concat("_", side), Str.fromInt(sid))), 1, pct(pre, Str.concat(side, Str.fromInt(sid))), tupSlots(ty), armPrefix, armI, false) +def emitTuplePeel(pre: String, sid: Int, scrut: String, tys: List[String], comps: List[String], armPrefix: String, armI: Int, last: Bool, strs: List[String], fin: Bool): String = + join(peelLeft(pre, sid, scrut), join(peelRight(pre, sid, scrut), join(peelBrTn(pre, sid), join(peelTnLab(pre, sid), emitTupleCont(pre, sid, tys, comps, armPrefix, armI, last, strs, fin))))) + +def emitTupleCont(pre: String, sid: Int, tys: List[String], comps: List[String], armPrefix: String, armI: Int, last: Bool, strs: List[String], fin: Bool): String = + join(if (slotInt(tys, 0)) peelUnboxL(pre, sid) else "", tupleSideTest(compAt(comps, 0), pre, sid, "l", slotInt(tys, 0), strs, emitTupleRest(pre, sid, tys, comps, armPrefix, armI, last, strs, fin), nextLab(armPrefix, armI, last))) + +def emitTupleRest(pre: String, sid: Int, tys: List[String], comps: List[String], armPrefix: String, armI: Int, last: Bool, strs: List[String], fin: Bool): String = + join(emitNest(pre, sid, "tl", headTy(tys), compAt(comps, 0), armPrefix, armI, last, strs), emitTupleRest2(pre, sid, tys, comps, armPrefix, armI, last, strs, fin)) + +def emitTupleRest2(pre: String, sid: Int, tys: List[String], comps: List[String], armPrefix: String, armI: Int, last: Bool, strs: List[String], fin: Bool): String = + if (List.len(tys) > 2) emitTuplePeel(Str.concat(pre, Str.concat("_n", Str.fromInt(sid))), sid + 1, pct(pre, Str.concat("tr", Str.fromInt(sid))), List.tail(tys), tailComps(comps), armPrefix, armI, last, strs, fin) else join(emitNest(pre, sid, "tr", secondTy(tys), compAt(comps, 1), armPrefix, armI, last, strs), join(if (slotInt(tys, 1)) peelUnboxR(pre, sid) else "", tupleSideTest(compAt(comps, 1), pre, sid, "r", slotInt(tys, 1), strs, if (fin) tupleBrOk(armPrefix, armI) else "", nextLab(armPrefix, armI, last)))) + +def emitNest(pre: String, sid: Int, side: String, ty: String, comp: String, armPrefix: String, armI: Int, last: Bool, strs: List[String]): String = + if (!isTupTy(ty)) "" else emitTuplePeel(Str.concat(pre, Str.concat(Str.concat("_", side), Str.fromInt(sid))), 1, pct(pre, Str.concat(side, Str.fromInt(sid))), tupSlots(ty), nestComps(comp), armPrefix, armI, last, strs, false) + +def tupleSideTest(comp: String, pre: String, sid: Int, side: String, isInt: Bool, strs: List[String], cont: String, fail: String): String = + if (isIntPat(comp) || isBoolPat(comp)) tupleIntTest(comp, pre, sid, side, isInt, cont, fail) else if (isStrPat(comp)) tupleStrTest(comp, pre, sid, side, strs, cont, fail) else cont + +def tupleContStem(pre: String, side: String): String = + Str.concat(pre, Str.concat("_tok", side)) + +def tupleSideVal(pre: String, sid: Int, side: String): String = + pct(pre, Str.concat(if (side == "l") "tl" else "tr", Str.fromInt(sid))) + +def tupleSideI64(pre: String, sid: Int, side: String): String = + pct(pre, Str.concat(if (side == "l") "tvl" else "tv", Str.fromInt(sid))) + +def tupleIntTest(comp: String, pre: String, sid: Int, side: String, isInt: Bool, cont: String, fail: String): String = + if (!isInt) cont else join(line(Str.concat(tmp(pre, Str.concat("tie", side)), Str.concat("icmp eq i64 ", Str.concat(tupleSideI64(pre, sid, side), Str.concat(", ", if (isBoolPat(comp)) boolPatLit(comp) else comp))))), join(line(Str.concat("br i1 ", Str.concat(pct(pre, Str.concat("tie", side)), Str.concat(", label %", Str.concat(tupleContStem(pre, side), Str.concat(", label %", fail)))))), join(lab(tupleContStem(pre, side)), cont))) + +def tupleStrTest(comp: String, pre: String, sid: Int, side: String, strs: List[String], cont: String, fail: String): String = + join(tupleStrCompare(unquote(comp), pre, side, tupleSideVal(pre, sid, side), strs), join(line(Str.concat("br i1 ", Str.concat(pct(pre, Str.concat("tseq", side)), Str.concat(", label %", Str.concat(tupleContStem(pre, side), Str.concat(", label %", fail)))))), join(lab(tupleContStem(pre, side)), cont))) + +def tupleStrCompare(s: String, pre: String, side: String, val: String, strs: List[String]): String = + join(line(Str.concat(tmp(pre, Str.concat("tsgep", side)), Str.concat("getelementptr inbounds [", Str.concat(Str.fromInt(strArr(s)), Str.concat(" x i8], ptr @.str", Str.concat(Str.fromInt(strIndex(strs, s, 0)), ", i64 0, i64 0")))))), join(line(Str.concat(tmp(pre, Str.concat("tslit", side)), Str.concat("call ptr @sz_string_from_cstr(ptr ", Str.concat(pct(pre, Str.concat("tsgep", side)), ")")))), join(line(Str.concat(tmp(pre, Str.concat("tseqi", side)), Str.concat("call i32 @sz_string_eq(ptr ", Str.concat(val, Str.concat(", ptr ", Str.concat(pct(pre, Str.concat("tslit", side)), ")")))))), join(relPtr(pct(pre, Str.concat("tslit", side))), line(Str.concat(tmp(pre, Str.concat("tseq", side)), Str.concat("icmp ne i32 ", Str.concat(pct(pre, Str.concat("tseqi", side)), ", 0")))))))) def headTy(tys: List[String]): String = if (List.isEmpty(tys)) "" else List.at(tys, 0) diff --git a/examples/kernel/facts.scuzz_verify b/examples/kernel/facts.scuzz_verify index 0a8b9716..5a8119cc 100644 --- a/examples/kernel/facts.scuzz_verify +++ b/examples/kernel/facts.scuzz_verify @@ -109,6 +109,24 @@ def alternativeString(s: String): Bool = def alternativeGuard(n: Int): Bool = Main.alternativeGuard(n) == (n == 2 || n == 19) +def tupleLitInt(n: Int, s: String): Bool = + Main.tupleLitInt((n, s)) == (if (n == 1) Str.concat("one:", s) else Str.concat(Str.fromInt(n), s)) + +def tupleLitStr(s: String, n: Int): Bool = + Main.tupleLitStr((s, n)) == (if (s == "a") 1 else 2) + +def tupleLitBool(b: Bool, n: Int): Bool = + Main.tupleLitBool((b, n)) == (if (b) n else 0 - n) + +def tupleLitTri(n: Int, s: String, b: Bool): Bool = + Main.tupleLitTri((n, s, b)) == (if (n == 0) 1 else if (s == "a") 2 else if (b) 3 else 4) + +def tupleLitNest(a: Int, b: Int, s: String): Bool = + Main.tupleLitNest(((a, b), s)) == (if (a == 1 && b == 2) 1 else if (a == 1) 2 else if (b == 2) 3 else 4) + +def tupleLitConcrete(): Bool = + Main.tupleLitInt((1, "a")) == "one:a" && Main.tupleLitInt((2, "b")) == "2b" && Main.tupleLitStr(("a", 9)) == 1 && Main.tupleLitStr(("z", 9)) == 2 && Main.tupleLitBool((true, 3)) == 3 && Main.tupleLitBool((false, 3)) == 0 - 3 && Main.tupleLitTri((0, "z", false)) == 1 && Main.tupleLitTri((7, "a", false)) == 2 && Main.tupleLitTri((7, "z", true)) == 3 && Main.tupleLitTri((7, "z", false)) == 4 && Main.tupleLitNest(((1, 2), "x")) == 1 && Main.tupleLitNest(((1, 9), "x")) == 2 && Main.tupleLitNest(((9, 2), "x")) == 3 && Main.tupleLitNest(((9, 9), "x")) == 4 + def alternativeQuoted(): Bool = Main.alternativeString("x | y") && !Main.alternativeString("x") diff --git a/examples/kernel/src/Main.scuzz b/examples/kernel/src/Main.scuzz index 36345570..941aae3d 100644 --- a/examples/kernel/src/Main.scuzz +++ b/examples/kernel/src/Main.scuzz @@ -932,6 +932,40 @@ def alternativeGuard(n: Int): Bool = case _ => false } +def tupleLitInt(p: (Int, String)): String = + p match { + case (1, s) => Str.concat("one:", s) + case (n, s) => Str.concat(Str.fromInt(n), s) + } + +def tupleLitStr(p: (String, Int)): Int = + p match { + case ("a", _) => 1 + case _ => 2 + } + +def tupleLitBool(p: (Bool, Int)): Int = + p match { + case (true, n) => n + case (false, n) => 0 - n + } + +def tupleLitTri(p: (Int, String, Bool)): Int = + p match { + case (0, _, _) => 1 + case (_, "a", _) => 2 + case (_, _, true) => 3 + case _ => 4 + } + +def tupleLitNest(p: ((Int, Int), String)): Int = + p match { + case ((1, 2), _) => 1 + case ((1, _), _) => 2 + case ((_, 2), _) => 3 + case _ => 4 + } + def constructorAlternatives(p: LiteralPacket): Bool = p match { case LiteralPacket.Fields("ready", 2, true, 7) | LiteralPacket.Text("done") | LiteralPacket.Number(19) => true From e8f49edef9ca8ac90df5607f5c75057f0aa29e3c Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 16 Sep 2026 10:55:50 -0400 Subject: [PATCH 03/23] Pin Map, Set, and List payload types at construction and use. Map.empty, Set.empty, and List.empty produced a bare handle. The payload never pinned, so mixed keys and values passed check. A parameterized handle now pins key, value, and element wants for Map.set, Map.get, Map.contains, Map.remove, Map.getOrElse, Set.add, Set.contains, Set.remove, Set.union, and Set.intersect. Map.set and Set.add on a bare handle return the payload from the argument types, so Map.set(Map.empty(), "a", 1) is Map[String, Int]. List.cons pins the list argument from the element type. List.concat pins the second list from the first. The tyck oracle covers pinned construction, key, value, and element mismatches, and List.cons. LLVM IR fixed-point converges. The kernel suite passes with the new compiler. --- docs/gaps.md | 2 +- examples/compiler/src/Check.scuzz | 64 ++++++++++++++++++++++++++++--- examples/tyck/src/Main.scuzz | 5 ++- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/docs/gaps.md b/docs/gaps.md index b26b8be0..78fe497b 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -37,7 +37,7 @@ Do not add library publishing, git or registry deps, or `scuzz add`. Path deps s Resolve these gaps when they prevent ordinary language use. -1. **Checker and emit residuals** — `Map.empty`, `Set.empty`, and `List.empty` still use a bare constructor. A Queue or Deferred handle has no payload until the first offer or complete. Param letters (`A`/`E`) still unify. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest: a constructor pattern inside a tuple or constructor field fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. +1. **Checker and emit residuals** — A Queue or Deferred handle has no payload until the first offer or complete. Param letters (`A`/`E`) still unify. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest: a constructor pattern inside a tuple or constructor field fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. 2. **Compile-time performance** — `scuzz check examples/compiler` is 16 s. A cold `scuzz build examples/tyck` is 1 m 10 s. Remaining cost: RC retain/release churn and `sz_list_concat` in string building. Coverage still parses a compiled graph that differs from live. diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index 12d488bf..3b6bafaf 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -305,7 +305,37 @@ def zipCheckEq(callee: String, w: String, got: String, want: List[String], args: if (tyEqEn(got, w, ens)) zipCheck(callee, kitWantFromTy(callee, if (acc == "") got else acc, want), args, env, funs, ens, span, if (acc == "") got else acc) else argMismatch(callee, w, got, asp) def kitWantFromTy(f: String, handleTy: String, want: List[String]): List[String] = - if (List.isEmpty(want)) want else if (f == "Queue.offer" || f == "Deferred.complete") offerElem(handleTy) :: List.tail(want) else want + if (List.isEmpty(want)) want else if (f == "Queue.offer" || f == "Deferred.complete") offerElem(handleTy) :: List.tail(want) else if (f == "Map.set" || f == "Map.getOrElse") mapKVWant(f, handleTy, want) else if (f == "Map.get" || f == "Map.contains" || f == "Map.remove") mapKWant(f, handleTy, want) else if (f == "Set.add" || f == "Set.contains" || f == "Set.remove") setElemWant(f, handleTy, want) else if (f == "Set.union" || f == "Set.intersect") setSameWant(f, handleTy, want) else if (f == "List.cons") consListWant(handleTy) :: List.tail(want) else if (f == "List.concat") concatWant(handleTy, want) else want + +def isMapTy(t: String): Bool = + Str.startsWith(t, "Map[") + +def isSetTy(t: String): Bool = + Str.startsWith(t, "Set[") + +def mapKVWant(f: String, handleTy: String, want: List[String]): List[String] = + if (isMapTy(handleTy) && List.len(want) > 1) mapKeyOf(handleTy) :: mapValOf(handleTy) :: List.tail(List.tail(want)) else want + +def mapKWant(f: String, handleTy: String, want: List[String]): List[String] = + if (isMapTy(handleTy)) mapKeyOf(handleTy) :: List.tail(want) else want + +def setElemWant(f: String, handleTy: String, want: List[String]): List[String] = + if (isSetTy(handleTy)) elemOf(handleTy) :: List.tail(want) else want + +def setSameWant(f: String, handleTy: String, want: List[String]): List[String] = + if (isSetTy(handleTy)) handleTy :: List.tail(want) else want + +def consListWant(elem: String): String = + Str.concat("List[", Str.concat(elem, "]")) + +def concatWant(handleTy: String, want: List[String]): List[String] = + if (isListTy(handleTy) && handleTy != "List") handleTy :: List.tail(want) else want + +def mapKeyOf(ty: String): String = + mapKeyOf2(splitComma(elemOf(ty))) + +def mapKeyOf2(parts: List[String]): String = + if (List.isEmpty(parts)) "A" else List.at(parts, 0) def offerElem(handleTy: String): String = if (Str.startsWith(handleTy, "Queue[") || Str.startsWith(handleTy, "Deferred[")) elemOf(handleTy) else "A" @@ -350,16 +380,40 @@ def isTup(t: String): Bool = Type.isTupStr(t) def checkKnown(f: String, want: List[String], ret: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = - if (List.len(want) != List.len(args)) bad(Str.concat(f, Str.concat(" expects ", Str.concat(Str.fromInt(List.len(want)), Str.concat(" args, got ", Str.fromInt(List.len(args)))))), span) else checkKnownRet(f, zipCheck(f, want, args, env, funs, ens, span, ""), ret) + if (List.len(want) != List.len(args)) bad(Str.concat(f, Str.concat(" expects ", Str.concat(Str.fromInt(List.len(want)), Str.concat(" args, got ", Str.fromInt(List.len(args)))))), span) else checkKnownRet(f, zipCheck(f, want, args, env, funs, ens, span, ""), ret, args, env, funs, ens) + +def checkKnownRet(f: String, o: Out, ret: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En]): Out = + if (hasErr(o)) o else ok(kitRetFromTy(f, tyStr(o), emptyPinRet(f, tyStr(o), args, env, funs, ens, substKitE(ret, tyStr(o))))) + +def emptyPinRet(f: String, handleTy: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], ret: String): String = + if (f == "Map.set" && handleTy == "Map") emptyMapRet(argTyAt(args, 1, env, funs, ens), argTyAt(args, 2, env, funs, ens), ret) else if (f == "Set.add" && handleTy == "Set") emptySetRet(argTyAt(args, 1, env, funs, ens), ret) else ret + +def argTyAt(args: List[Expr], k: Int, env: List[(String, Ty)], funs: Ftab, ens: List[En]): String = + if (k >= List.len(args)) "" else argTyOut(infer(stripNamed(List.at(args, k)), env, funs, ens)) + +def argTyOut(o: Out): String = + if (hasErr(o)) "" else tyStr(o) -def checkKnownRet(f: String, o: Out, ret: String): Out = - if (hasErr(o)) o else ok(kitRetFromTy(f, tyStr(o), substKitE(ret, tyStr(o)))) +def emptyMapRet(k: String, v: String, ret: String): String = + if (k == "" || v == "") ret else Str.concat("Map[", Str.concat(k, Str.concat(", ", Str.concat(v, "]")))) + +def emptySetRet(x: String, ret: String): String = + if (x == "") ret else Str.concat("Set[", Str.concat(x, "]")) def substKitE(ret: String, ty: String): String = if (ty == "") ret else substTy(ret, "E", ty) def kitRetFromTy(f: String, ty: String, ret: String): String = - if (f == "List.head") kitRetOption(ty) else if (f == "Map.get") kitRetMapOption(ty) else if (f == "List.at") elemOf(ty) else if (f == "List.tail" || f == "List.reverse" || f == "List.concat" || f == "List.last") kitRetList(ty, ret) else if (f == "List.cons") kitRetCons(ty, ret) else if (f == "IO.pure") ioTy("String", ty) else if (f == "IO.fail") Str.concat("IO[", Str.concat(if (ty == "") "String" else ty, ", A]")) else if (f == "Deferred.get") defGetRet(ty) else if (f == "Queue.take") queueTakeRet(ty) else ret + if (f == "List.head") kitRetOption(ty) else if (f == "Map.get") kitRetMapOption(ty) else if (f == "Map.getOrElse") mapGetOrElseRet(ty, ret) else if (f == "Map.set" || f == "Map.remove") mapSameRet(ty, ret) else if (f == "Set.add" || f == "Set.remove" || f == "Set.union" || f == "Set.intersect") setSameRet(ty, ret) else if (f == "List.at") elemOf(ty) else if (f == "List.tail" || f == "List.reverse" || f == "List.concat" || f == "List.last") kitRetList(ty, ret) else if (f == "List.cons") kitRetCons(ty, ret) else if (f == "IO.pure") ioTy("String", ty) else if (f == "IO.fail") Str.concat("IO[", Str.concat(if (ty == "") "String" else ty, ", A]")) else if (f == "Deferred.get") defGetRet(ty) else if (f == "Queue.take") queueTakeRet(ty) else ret + +def mapGetOrElseRet(ty: String, ret: String): String = + if (isMapTy(ty)) mapValOf(ty) else ret + +def mapSameRet(ty: String, ret: String): String = + if (isMapTy(ty)) ty else ret + +def setSameRet(ty: String, ret: String): String = + if (isSetTy(ty)) ty else ret def defGetRet(ty: String): String = if (Str.startsWith(ty, "Deferred[")) ioTy("String", elemOf(ty)) else "IO[A]" diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index 9065df4c..acce509f 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -341,7 +341,7 @@ def tyckFlow(): Bool = tyckDiff(srcUnBool(), wantUnBool()) && tyckDiff(srcIfMis(), wantIfMis()) && tyckDiff(srcMapHandle(), wantMapHandle()) && tyckDiff(srcJoinMis(), wantJoinMis()) && tyckDiff(srcNope(), wantNope()) && tyckDiff(srcStrUnk(), wantStrUnk()) && tyckDiff(srcStrConcatTy(), wantStrConcatTy()) && tyckDiff(srcSysExit(), wantSysExit()) && unaryNumTypes() def tyckRes(): Bool = - tyckDiff(srcResArity(), wantResArity()) && tyckDiff(srcResDraw(), wantResDraw()) && tyckDiff(srcResLam(), wantResLam()) && tyckDiff(srcResUnk(), wantResUnk()) && tyckDiff(srcJsonUnk(), wantJsonUnk()) && tyckDiff(srcDefUnk(), wantDefUnk()) && tyckDiff(srcDefMis(), wantDefMis()) && tyckDiff(srcQueUnk(), wantQueUnk()) && tyckDiff(srcQueMis(), wantQueMis()) && tyckDiff(srcQueTake(), wantQueTake()) && tyckDiff(srcQuePay(), wantQuePay()) && tyckDiff(srcNetStr(), wantNetStr()) + tyckDiff(srcResArity(), wantResArity()) && tyckDiff(srcResDraw(), wantResDraw()) && tyckDiff(srcResLam(), wantResLam()) && tyckDiff(srcResUnk(), wantResUnk()) && tyckDiff(srcJsonUnk(), wantJsonUnk()) && tyckDiff(srcDefUnk(), wantDefUnk()) && tyckDiff(srcDefMis(), wantDefMis()) && tyckDiff(srcQueUnk(), wantQueUnk()) && tyckDiff(srcQueMis(), wantQueMis()) && tyckDiff(srcQueTake(), wantQueTake()) && tyckDiff(srcQuePay(), wantQuePay()) && tyckDiff(srcNetStr(), wantNetStr()) && emptyCtorTypes() def rejects(src: String, msg: String): Bool = Str.contains(Check.check(src), msg) @@ -1020,6 +1020,9 @@ def value(r: Result[Problem, Int]): Int = r match { def unaryNumTypes(): Bool = Check.check("def right(): Float = -1.5") == "[]" && Check.check("def right(n: Float): Float = -n") == "[]" && Check.check("def right(): Int = -1") == "[]" && rejects("def wrong(): Bool = -true", "expected Int or Float, got Bool") +def emptyCtorTypes(): Bool = + Check.check("def right(): Map[String, Int] = Map.set(Map.empty(), \"a\", 1)") == "[]" && rejects("def wrong(): Map[String, String] = Map.set(Map.empty(), \"a\", 1)", "does not match declared") && Check.check("def right(m: Map[String, Int]): Map[String, Int] = Map.set(m, \"b\", 2)") == "[]" && rejects("def wrong(m: Map[String, Int]): Map[String, Int] = Map.set(m, 1, 2)", "Map.set arg type mismatch: expected String, got Int") && rejects("def wrong(m: Map[String, Int]): Map[String, Int] = Map.set(m, \"b\", \"x\")", "Map.set arg type mismatch: expected Int, got String") && Check.check("def right(m: Map[String, Int]): Option[Int] = Map.get(m, \"k\")") == "[]" && rejects("def wrong(m: Map[String, Int]): Option[Int] = Map.get(m, 1)", "Map.get arg type mismatch: expected String, got Int") && Check.check("def right(m: Map[String, Int]): Int = Map.getOrElse(m, \"k\", 0)") == "[]" && rejects("def wrong(m: Map[String, Int]): Int = Map.getOrElse(m, \"k\", \"x\")", "Map.getOrElse arg type mismatch: expected Int, got String") && Check.check("def right(): Set[Int] = Set.add(Set.empty(), 1)") == "[]" && rejects("def wrong(): Set[String] = Set.add(Set.empty(), 1)", "does not match declared") && rejects("def wrong(s: Set[Int]): Bool = Set.contains(s, \"x\")", "Set.contains arg type mismatch: expected Int, got String") && rejects("def wrong(a: Set[Int], b: Set[String]): Set[Int] = Set.union(a, b)", "Set.union arg type mismatch: expected Set[Int], got Set[String]") && Check.check("def right(xs: List[Int]): List[Int] = List.cons(1, xs)") == "[]" && rejects("def wrong(xs: List[Int]): List[String] = List.cons(\"s\", xs)", "List.cons arg type mismatch: expected List[String], got List[Int]") && Check.check("def right(): List[Int] = List.cons(1, List.empty())") == "[]" + def matchArmTypes(): Bool = Check.check("""enum E: case A From 5c790cb1342415c066c2352c13b4442d2efad620 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 16 Sep 2026 11:15:32 -0400 Subject: [PATCH 04/23] Revert dead Map, Set, and List kit helpers. Audit against the pre-change compiler showed the pinning behaviors were already live. Map.*, Set.*, List.cons, and List.concat route through genericArgs, which binds payload types structurally. The zipCheck helpers added in the previous pass were unreachable, as were the pre-existing Map.get, List.at, List.cons, and List.concat branches of kitRetFromTy. Delete all of them. The tyck oracle cases stay. They pass before and after this change. They now lock in the generic-path pinning semantics they were written to prove. LLVM IR fixed-point converges. The kernel suite passes. --- examples/compiler/src/Check.scuzz | 76 ++----------------------------- 1 file changed, 5 insertions(+), 71 deletions(-) diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index 3b6bafaf..c32a2438 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -305,37 +305,7 @@ def zipCheckEq(callee: String, w: String, got: String, want: List[String], args: if (tyEqEn(got, w, ens)) zipCheck(callee, kitWantFromTy(callee, if (acc == "") got else acc, want), args, env, funs, ens, span, if (acc == "") got else acc) else argMismatch(callee, w, got, asp) def kitWantFromTy(f: String, handleTy: String, want: List[String]): List[String] = - if (List.isEmpty(want)) want else if (f == "Queue.offer" || f == "Deferred.complete") offerElem(handleTy) :: List.tail(want) else if (f == "Map.set" || f == "Map.getOrElse") mapKVWant(f, handleTy, want) else if (f == "Map.get" || f == "Map.contains" || f == "Map.remove") mapKWant(f, handleTy, want) else if (f == "Set.add" || f == "Set.contains" || f == "Set.remove") setElemWant(f, handleTy, want) else if (f == "Set.union" || f == "Set.intersect") setSameWant(f, handleTy, want) else if (f == "List.cons") consListWant(handleTy) :: List.tail(want) else if (f == "List.concat") concatWant(handleTy, want) else want - -def isMapTy(t: String): Bool = - Str.startsWith(t, "Map[") - -def isSetTy(t: String): Bool = - Str.startsWith(t, "Set[") - -def mapKVWant(f: String, handleTy: String, want: List[String]): List[String] = - if (isMapTy(handleTy) && List.len(want) > 1) mapKeyOf(handleTy) :: mapValOf(handleTy) :: List.tail(List.tail(want)) else want - -def mapKWant(f: String, handleTy: String, want: List[String]): List[String] = - if (isMapTy(handleTy)) mapKeyOf(handleTy) :: List.tail(want) else want - -def setElemWant(f: String, handleTy: String, want: List[String]): List[String] = - if (isSetTy(handleTy)) elemOf(handleTy) :: List.tail(want) else want - -def setSameWant(f: String, handleTy: String, want: List[String]): List[String] = - if (isSetTy(handleTy)) handleTy :: List.tail(want) else want - -def consListWant(elem: String): String = - Str.concat("List[", Str.concat(elem, "]")) - -def concatWant(handleTy: String, want: List[String]): List[String] = - if (isListTy(handleTy) && handleTy != "List") handleTy :: List.tail(want) else want - -def mapKeyOf(ty: String): String = - mapKeyOf2(splitComma(elemOf(ty))) - -def mapKeyOf2(parts: List[String]): String = - if (List.isEmpty(parts)) "A" else List.at(parts, 0) + if (List.isEmpty(want)) want else if (f == "Queue.offer" || f == "Deferred.complete") offerElem(handleTy) :: List.tail(want) else want def offerElem(handleTy: String): String = if (Str.startsWith(handleTy, "Queue[") || Str.startsWith(handleTy, "Deferred[")) elemOf(handleTy) else "A" @@ -380,40 +350,16 @@ def isTup(t: String): Bool = Type.isTupStr(t) def checkKnown(f: String, want: List[String], ret: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = - if (List.len(want) != List.len(args)) bad(Str.concat(f, Str.concat(" expects ", Str.concat(Str.fromInt(List.len(want)), Str.concat(" args, got ", Str.fromInt(List.len(args)))))), span) else checkKnownRet(f, zipCheck(f, want, args, env, funs, ens, span, ""), ret, args, env, funs, ens) - -def checkKnownRet(f: String, o: Out, ret: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En]): Out = - if (hasErr(o)) o else ok(kitRetFromTy(f, tyStr(o), emptyPinRet(f, tyStr(o), args, env, funs, ens, substKitE(ret, tyStr(o))))) + if (List.len(want) != List.len(args)) bad(Str.concat(f, Str.concat(" expects ", Str.concat(Str.fromInt(List.len(want)), Str.concat(" args, got ", Str.fromInt(List.len(args)))))), span) else checkKnownRet(f, zipCheck(f, want, args, env, funs, ens, span, ""), ret) -def emptyPinRet(f: String, handleTy: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], ret: String): String = - if (f == "Map.set" && handleTy == "Map") emptyMapRet(argTyAt(args, 1, env, funs, ens), argTyAt(args, 2, env, funs, ens), ret) else if (f == "Set.add" && handleTy == "Set") emptySetRet(argTyAt(args, 1, env, funs, ens), ret) else ret - -def argTyAt(args: List[Expr], k: Int, env: List[(String, Ty)], funs: Ftab, ens: List[En]): String = - if (k >= List.len(args)) "" else argTyOut(infer(stripNamed(List.at(args, k)), env, funs, ens)) - -def argTyOut(o: Out): String = - if (hasErr(o)) "" else tyStr(o) - -def emptyMapRet(k: String, v: String, ret: String): String = - if (k == "" || v == "") ret else Str.concat("Map[", Str.concat(k, Str.concat(", ", Str.concat(v, "]")))) - -def emptySetRet(x: String, ret: String): String = - if (x == "") ret else Str.concat("Set[", Str.concat(x, "]")) +def checkKnownRet(f: String, o: Out, ret: String): Out = + if (hasErr(o)) o else ok(kitRetFromTy(f, tyStr(o), substKitE(ret, tyStr(o)))) def substKitE(ret: String, ty: String): String = if (ty == "") ret else substTy(ret, "E", ty) def kitRetFromTy(f: String, ty: String, ret: String): String = - if (f == "List.head") kitRetOption(ty) else if (f == "Map.get") kitRetMapOption(ty) else if (f == "Map.getOrElse") mapGetOrElseRet(ty, ret) else if (f == "Map.set" || f == "Map.remove") mapSameRet(ty, ret) else if (f == "Set.add" || f == "Set.remove" || f == "Set.union" || f == "Set.intersect") setSameRet(ty, ret) else if (f == "List.at") elemOf(ty) else if (f == "List.tail" || f == "List.reverse" || f == "List.concat" || f == "List.last") kitRetList(ty, ret) else if (f == "List.cons") kitRetCons(ty, ret) else if (f == "IO.pure") ioTy("String", ty) else if (f == "IO.fail") Str.concat("IO[", Str.concat(if (ty == "") "String" else ty, ", A]")) else if (f == "Deferred.get") defGetRet(ty) else if (f == "Queue.take") queueTakeRet(ty) else ret - -def mapGetOrElseRet(ty: String, ret: String): String = - if (isMapTy(ty)) mapValOf(ty) else ret - -def mapSameRet(ty: String, ret: String): String = - if (isMapTy(ty)) ty else ret - -def setSameRet(ty: String, ret: String): String = - if (isSetTy(ty)) ty else ret + if (f == "List.head") kitRetOption(ty) else if (f == "List.tail" || f == "List.reverse" || f == "List.last") kitRetList(ty, ret) else if (f == "IO.pure") ioTy("String", ty) else if (f == "IO.fail") Str.concat("IO[", Str.concat(if (ty == "") "String" else ty, ", A]")) else if (f == "Deferred.get") defGetRet(ty) else if (f == "Queue.take") queueTakeRet(ty) else ret def defGetRet(ty: String): String = if (Str.startsWith(ty, "Deferred[")) ioTy("String", elemOf(ty)) else "IO[A]" @@ -424,21 +370,9 @@ def queueTakeRet(ty: String): String = def kitRetOption(ty: String): String = Str.concat("Option[", Str.concat(elemOf(ty), "]")) -def kitRetMapOption(ty: String): String = - Str.concat("Option[", Str.concat(mapValOf(ty), "]")) - -def mapValOf(ty: String): String = - mapValOf2(splitComma(elemOf(ty))) - -def mapValOf2(parts: List[String]): String = - if (List.isEmpty(parts) || List.isEmpty(List.tail(parts))) "A" else List.at(parts, 1) - def kitRetList(ty: String, ret: String): String = if (isListTy(ty)) ty else ret -def kitRetCons(ty: String, ret: String): String = - if (ty == "") ret else Str.concat("List[", Str.concat(ty, "]")) - def resolveCall(f: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = resolveCall2(f, args, env, funs, ens, span) From da46e3633a9361eef5839dbb6303906d1f10d840 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 16 Sep 2026 11:53:39 -0400 Subject: [PATCH 05/23] Skip the coverage no-op call when coverage is off. sz_panic_push_src runs at every emitted function entry. It called coverage_hit even when SCUZZ_COVERAGE_DUMP was unset. coverage_hit is too large to inline, so the no-op path paid a call per source span. A gprof profile of scuzz check examples/compiler put coverage_hit at about 15 percent of self time. coverage_probe now sets a coverage_off flag when the dump path is unset. sz_coverage_hit and sz_panic_push_src skip the coverage_hit call when the flag is set. sz_coverage_env_refresh clears the flag. scuzz check examples/compiler drops from 21.6 s to 20.6 s wall. coverage_hit no longer appears in the flat profile. Runtime tests, LLVM IR fixed-point, tyck oracle, and the kernel suite pass. --- crates/runtime/src/runtime.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/runtime/src/runtime.c b/crates/runtime/src/runtime.c index f11565b0..56b43771 100644 --- a/crates/runtime/src/runtime.c +++ b/crates/runtime/src/runtime.c @@ -283,6 +283,7 @@ typedef struct CoverageHit { static CoverageHit *coverage_hits[256]; static char *coverage_path; static int coverage_probed; +static int coverage_off; static void coverage_clear(void) { size_t i; @@ -307,8 +308,10 @@ static void coverage_probe(void) { return; coverage_probed = 1; path = getenv("SCUZZ_COVERAGE_DUMP"); - if (!path || !*path) + if (!path || !*path) { + coverage_off = 1; return; + } coverage_path = malloc(strlen(path) + 1); if (!coverage_path) sz_panic("coverage: out of memory"); @@ -319,6 +322,7 @@ static void coverage_probe(void) { void sz_coverage_env_refresh(void) { coverage_clear(); coverage_probed = 0; + coverage_off = 0; } static void coverage_hit(const char *loc) { @@ -353,13 +357,16 @@ static void coverage_hit(const char *loc) { } void sz_coverage_hit(const char *loc) { + if (coverage_off) + return; coverage_hit(loc); } void sz_panic_push_src(const char *loc) { if (!loc || !loc[0]) return; - coverage_hit(loc); + if (!coverage_off) + coverage_hit(loc); if (g_panic_src_n < SZ_PANIC_SRC_MAX) g_panic_src[g_panic_src_n++] = loc; } From f7710bf0bc2b08d620f10b9552e0ea4f2ca047cc Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 16 Sep 2026 12:19:49 -0400 Subject: [PATCH 06/23] Pin Queue and Deferred payloads from nested offers. refineEnv only inspected the top level of each for-bind value. An offer inside IO.both, a lambda, or a match arm never pinned the handle payload, so a take afterwards kept the loose type. refineEnv is now a full expression fold. Any Queue.offer or Deferred.complete in the bind value pins the payload in source order. An offer statically constrains the payload wherever it appears. A payload seen only through a lambda parameter still does not pin. The tyck oracle covers nested offers in IO.both, IO.foreach lambdas, and if expressions, plus Deferred.complete, with consistent and mismatched takes. LLVM IR fixed-point converges. The kernel suite passes with the new compiler. --- docs/gaps.md | 2 +- examples/compiler/src/Check.scuzz | 33 +++++++++++++++++++++++-- examples/tyck/src/Main.scuzz | 40 ++++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/docs/gaps.md b/docs/gaps.md index 78fe497b..83cbcbab 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -37,7 +37,7 @@ Do not add library publishing, git or registry deps, or `scuzz add`. Path deps s Resolve these gaps when they prevent ordinary language use. -1. **Checker and emit residuals** — A Queue or Deferred handle has no payload until the first offer or complete. Param letters (`A`/`E`) still unify. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest: a constructor pattern inside a tuple or constructor field fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. +1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions. A payload seen only through a lambda parameter does not pin. Param letters (`A`/`E`) still unify. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest: a constructor pattern inside a tuple or constructor field fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. 2. **Compile-time performance** — `scuzz check examples/compiler` is 16 s. A cold `scuzz build examples/tyck` is 1 m 10 s. Remaining cost: RC retain/release churn and `sz_list_concat` in string building. Coverage still parses a compiled graph that differs from live. diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index c32a2438..05cd4ee8 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -888,13 +888,42 @@ def bindFor(d: Bool, name: String, ty: String, env: List[(String, Ty)], ens: Lis def refineEnv(e: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En]): List[(String, Ty)] = e match { - case Expr.ECall(f, args, _) => refineOfferCall(f, args, env, funs, ens) - case Expr.EMethod(recv, name, args, _) => refineOfferMeth(recv, name, args, env, funs, ens) + case Expr.ECall(f, args, _) => refineExprs(args, refineOfferCall(f, args, env, funs, ens), funs, ens) + case Expr.EMethod(recv, name, args, _) => refineExprs(args, refineEnv(recv, refineOfferMeth(recv, name, args, env, funs, ens), funs, ens), funs, ens) case Expr.EAscribe(inner, _, _) => refineEnv(inner, env, funs, ens) case Expr.ENamed(_, inner) => refineEnv(inner, env, funs, ens) + case Expr.ELam(_, _, body) => refineEnv(body, env, funs, ens) + case Expr.EPrint(inner, _) => refineEnv(inner, env, funs, ens) + case Expr.EField(recv, _, _) => refineEnv(recv, env, funs, ens) + case Expr.EBin(_, l, r, _) => refineEnv(r, refineEnv(l, env, funs, ens), funs, ens) + case Expr.EUn(_, inner, _) => refineEnv(inner, env, funs, ens) + case Expr.EIf(c, t, el, _) => refineEnv(el, refineEnv(t, refineEnv(c, env, funs, ens), funs, ens), funs, ens) + case Expr.EMatch(s, arms, _) => refineArms(arms, refineEnv(s, env, funs, ens), funs, ens) + case Expr.EFor(bs, body, _) => refineEnv(body, refineBinds(bs, env, funs, ens), funs, ens) + case Expr.EList(xs) => refineExprs(xs, env, funs, ens) + case Expr.ETuple(xs) => refineExprs(xs, env, funs, ens) case _ => env } +def refineExprs(xs: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En]): List[(String, Ty)] = + if (List.isEmpty(xs)) env else refineExprs(List.tail(xs), refineEnv(List.at(xs, 0), env, funs, ens), funs, ens) + +def refineArms(xs: List[Arm], env: List[(String, Ty)], funs: Ftab, ens: List[En]): List[(String, Ty)] = + if (List.isEmpty(xs)) env else refineArms(List.tail(xs), refineArm(List.at(xs, 0), env, funs, ens), funs, ens) + +def refineArm(a: Arm, env: List[(String, Ty)], funs: Ftab, ens: List[En]): List[(String, Ty)] = + a match { + case Arm(_, _, body) => refineEnv(body, env, funs, ens) + } + +def refineBinds(xs: List[Bind], env: List[(String, Ty)], funs: Ftab, ens: List[En]): List[(String, Ty)] = + if (List.isEmpty(xs)) env else refineBinds(List.tail(xs), refineBind(List.at(xs, 0), env, funs, ens), funs, ens) + +def refineBind(b: Bind, env: List[(String, Ty)], funs: Ftab, ens: List[En]): List[(String, Ty)] = + b match { + case Bind(_, _, v) => refineEnv(v, env, funs, ens) + } + def refineOfferCall(f: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En]): List[(String, Ty)] = if (f == "Queue.offer" || f == "Deferred.complete") refineOfferArgs(offerCtor(f), args, env, funs, ens) else env diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index acce509f..7438da88 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -341,7 +341,7 @@ def tyckFlow(): Bool = tyckDiff(srcUnBool(), wantUnBool()) && tyckDiff(srcIfMis(), wantIfMis()) && tyckDiff(srcMapHandle(), wantMapHandle()) && tyckDiff(srcJoinMis(), wantJoinMis()) && tyckDiff(srcNope(), wantNope()) && tyckDiff(srcStrUnk(), wantStrUnk()) && tyckDiff(srcStrConcatTy(), wantStrConcatTy()) && tyckDiff(srcSysExit(), wantSysExit()) && unaryNumTypes() def tyckRes(): Bool = - tyckDiff(srcResArity(), wantResArity()) && tyckDiff(srcResDraw(), wantResDraw()) && tyckDiff(srcResLam(), wantResLam()) && tyckDiff(srcResUnk(), wantResUnk()) && tyckDiff(srcJsonUnk(), wantJsonUnk()) && tyckDiff(srcDefUnk(), wantDefUnk()) && tyckDiff(srcDefMis(), wantDefMis()) && tyckDiff(srcQueUnk(), wantQueUnk()) && tyckDiff(srcQueMis(), wantQueMis()) && tyckDiff(srcQueTake(), wantQueTake()) && tyckDiff(srcQuePay(), wantQuePay()) && tyckDiff(srcNetStr(), wantNetStr()) && emptyCtorTypes() + tyckDiff(srcResArity(), wantResArity()) && tyckDiff(srcResDraw(), wantResDraw()) && tyckDiff(srcResLam(), wantResLam()) && tyckDiff(srcResUnk(), wantResUnk()) && tyckDiff(srcJsonUnk(), wantJsonUnk()) && tyckDiff(srcDefUnk(), wantDefUnk()) && tyckDiff(srcDefMis(), wantDefMis()) && tyckDiff(srcQueUnk(), wantQueUnk()) && tyckDiff(srcQueMis(), wantQueMis()) && tyckDiff(srcQueTake(), wantQueTake()) && tyckDiff(srcQuePay(), wantQuePay()) && tyckDiff(srcNetStr(), wantNetStr()) && emptyCtorTypes() && queueRefineTypes() def rejects(src: String, msg: String): Bool = Str.contains(Check.check(src), msg) @@ -1020,6 +1020,44 @@ def value(r: Result[Problem, Int]): Int = r match { def unaryNumTypes(): Bool = Check.check("def right(): Float = -1.5") == "[]" && Check.check("def right(n: Float): Float = -n") == "[]" && Check.check("def right(): Int = -1") == "[]" && rejects("def wrong(): Bool = -true", "expected Int or Float, got Bool") +def queueRefineTypes(): Bool = + Check.check("""@main def main: IO[Unit] = + for { + q <- Queue.unbounded() + _ <- IO.both(Queue.offer(q, 1), IO.pure(())) + n <- Queue.take(q) + _ <- IO.println(Str.fromInt(n)) + } yield () +""") == "[]" && rejects("""@main def main: IO[Unit] = + for { + q <- Queue.unbounded() + _ <- IO.both(Queue.offer(q, 1), IO.pure(())) + s <- Queue.take(q) + _ <- IO.println(s) + } yield () +""", "expected String, got Int") && rejects("""@main def main: IO[Unit] = + for { + q <- Queue.unbounded() + _ <- IO.foreach([1], x => Queue.offer(q, 2)) + s <- Queue.take(q) + _ <- IO.println(s) + } yield () +""", "expected String, got Int") && Check.check("""@main def main: IO[Unit] = + for { + q <- Queue.unbounded() + _ <- IO.foreach([1], x => Queue.offer(q, 2)) + n <- Queue.take(q) + _ <- IO.println(Str.fromInt(n)) + } yield () +""") == "[]" && rejects("@main def main: IO[Unit] =\n for {\n d <- Deferred.empty()\n _ <- IO.both(Deferred.complete(d, \"x\"), IO.pure(()))\n n <- Deferred.get(d)\n _ <- IO.println(Str.fromInt(n + 1))\n } yield ()\n", "arithmetic needs Int or Float") && Check.check("""@main def main: IO[Unit] = + for { + q <- Queue.unbounded() + _ <- if (true) Queue.offer(q, 1) else IO.pure(()) + n <- Queue.take(q) + _ <- IO.println(Str.fromInt(n)) + } yield () +""") == "[]" + def emptyCtorTypes(): Bool = Check.check("def right(): Map[String, Int] = Map.set(Map.empty(), \"a\", 1)") == "[]" && rejects("def wrong(): Map[String, String] = Map.set(Map.empty(), \"a\", 1)", "does not match declared") && Check.check("def right(m: Map[String, Int]): Map[String, Int] = Map.set(m, \"b\", 2)") == "[]" && rejects("def wrong(m: Map[String, Int]): Map[String, Int] = Map.set(m, 1, 2)", "Map.set arg type mismatch: expected String, got Int") && rejects("def wrong(m: Map[String, Int]): Map[String, Int] = Map.set(m, \"b\", \"x\")", "Map.set arg type mismatch: expected Int, got String") && Check.check("def right(m: Map[String, Int]): Option[Int] = Map.get(m, \"k\")") == "[]" && rejects("def wrong(m: Map[String, Int]): Option[Int] = Map.get(m, 1)", "Map.get arg type mismatch: expected String, got Int") && Check.check("def right(m: Map[String, Int]): Int = Map.getOrElse(m, \"k\", 0)") == "[]" && rejects("def wrong(m: Map[String, Int]): Int = Map.getOrElse(m, \"k\", \"x\")", "Map.getOrElse arg type mismatch: expected Int, got String") && Check.check("def right(): Set[Int] = Set.add(Set.empty(), 1)") == "[]" && rejects("def wrong(): Set[String] = Set.add(Set.empty(), 1)", "does not match declared") && rejects("def wrong(s: Set[Int]): Bool = Set.contains(s, \"x\")", "Set.contains arg type mismatch: expected Int, got String") && rejects("def wrong(a: Set[Int], b: Set[String]): Set[Int] = Set.union(a, b)", "Set.union arg type mismatch: expected Set[Int], got Set[String]") && Check.check("def right(xs: List[Int]): List[Int] = List.cons(1, xs)") == "[]" && rejects("def wrong(xs: List[Int]): List[String] = List.cons(\"s\", xs)", "List.cons arg type mismatch: expected List[String], got List[Int]") && Check.check("def right(): List[Int] = List.cons(1, List.empty())") == "[]" From a133601145f4d15b7c7363a55d205e0368ca8a67 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 16 Sep 2026 13:01:35 -0400 Subject: [PATCH 07/23] Reject nested composite patterns at check. Nested patterns in constructor fields and tuple components were broken in three ways. Constructor and tuple patterns in fields crashed the LLVM link. As-patterns, bare enum cases, or-patterns, float literals, and list literals in fields silently matched the wrong arm: Opt.Some(n @ 0) matched Some(5), W.V(Opt.None, _) matched V(Some(9), 2), and Opt.Some(0 | 1) matched Some(2). The checker now validates arm patterns and rejects nested composite patterns with a clear error. Literal, variable, wildcard, and cons patterns with simple parts stay valid in constructor fields. Literal, variable, wildcard, and nested tuple patterns stay valid in tuple components. Arm-level as-patterns and or-patterns are unchanged. The kernel example used two rejected forms. Opt.Some(n @ 0) is now Opt.Some(0), and Opt.Some(0 | 1) splits into two arms. Both were silently miscompiled before; runtime output is now correct for Some(5) and Some(2). The tyck oracle covers every rejected form and the supported forms. LLVM IR fixed-point converges. The kernel suite passes with the new compiler. --- docs/gaps.md | 2 +- examples/compiler/src/Check.scuzz | 52 +++++++++++++++++++++++++- examples/kernel/src/Main.scuzz | 5 ++- examples/tyck/src/Main.scuzz | 62 ++++++++++++++++++++++++++++++- 4 files changed, 116 insertions(+), 5 deletions(-) diff --git a/docs/gaps.md b/docs/gaps.md index 83cbcbab..df5df701 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -37,7 +37,7 @@ Do not add library publishing, git or registry deps, or `scuzz add`. Path deps s Resolve these gaps when they prevent ordinary language use. -1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions. A payload seen only through a lambda parameter does not pin. Param letters (`A`/`E`) still unify. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest: a constructor pattern inside a tuple or constructor field fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. +1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions. A payload seen only through a lambda parameter does not pin. Param letters (`A`/`E`) still unify. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest. A nested constructor, tuple, as, or cons pattern in a constructor field or tuple component fails check. A bare enum case, float literal, or list literal in those positions also fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. 2. **Compile-time performance** — `scuzz check examples/compiler` is 16 s. A cold `scuzz build examples/tyck` is 1 m 10 s. Remaining cost: RC retain/release churn and `sz_list_concat` in string building. Coverage still parses a compiled graph that differs from live. diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index 05cd4ee8..a77f1aaf 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -1044,7 +1044,57 @@ def inferMatch(s: Expr, arms: List[Arm], env: List[(String, Ty)], funs: Ftab, en inferMatch2(infer(s, env, funs, ens), arms, env, funs, ens, span) def inferMatch2(o: Out, arms: List[Arm], env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = - if (hasErr(o)) o else if (List.isEmpty(arms)) o else inferMatchRest(inferMatchChecked(altArmsErr(arms, tyStr(o), ens, span), List.at(arms, 0), tyStr(o), env, funs, ens), List.tail(arms), tyStr(o), env, funs, ens) + if (hasErr(o)) o else if (List.isEmpty(arms)) o else inferMatchNest(nestArmsErr(arms, span), arms, tyStr(o), env, funs, ens, span) + +def inferMatchNest(v: Out, arms: List[Arm], scrutTy: String, env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = + if (hasErr(v)) v else inferMatchRest(inferMatchChecked(altArmsErr(arms, scrutTy, ens, span), List.at(arms, 0), scrutTy, env, funs, ens), List.tail(arms), scrutTy, env, funs, ens) + +def nestArmsErr(arms: List[Arm], span: (String, Int)): Out = + if (List.isEmpty(arms)) ok("Unit") else nestArmsErr2(nestArmErr(List.at(arms, 0)), List.tail(arms), span) + +def nestArmsErr2(err: String, rest: List[Arm], span: (String, Int)): Out = + if (Str.len(err) > 0) bad(err, span) else nestArmsErr(rest, span) + +def nestArmErr(arm: Arm): String = + arm match { + case Arm(pat, _, _) => nestAltErr(pat, Parse.patternAlternativeAt(pat, 0, 0)) + } + +def nestAltErr(pat: String, i: Int): String = + if (i < 0) nestPatErr(trimName(pat)) else nestAltErr2(nestPatErr(trimName(Str.take(pat, i))), Str.drop(pat, i + 3)) + +def nestAltErr2(err: String, rest: String): String = + if (Str.len(err) > 0) err else nestAltErr(rest, Parse.patternAlternativeAt(rest, 0, 0)) + +def nestPatErr(pat: String): String = + if (asAt(pat, 0) >= 0) nestPatErr(trimName(Str.drop(pat, asAt(pat, 0) + 3))) else if (consAt(pat, 0) >= 0) nestConsHeadErr(trimName(Str.take(pat, consAt(pat, 0))), trimName(Str.drop(pat, consAt(pat, 0) + 4))) else if (isTuplePat(pat)) nestFieldsErr(splitComma(Str.slice(pat, 1, Str.len(pat) - 1)), true) else if (patBind(pat) == "") "" else nestFieldsErr(splitComma(patBind(pat)), false) + +def nestConsHeadErr(head: String, tail: String): String = + if (asAt(head, 0) >= 0) "nested patterns are not supported here" else if (nestSimple(head)) nestPatErr(tail) else "nested patterns are not supported here" + +def nestSimple(s: String): Bool = + s == "" || s == "_" || altStrPat(s) || altIntPat(s) || altBoolPat(s) || isBareName(s) && dotAt(s, 0) < 0 + +def nestFieldsErr(fs: List[String], inTuple: Bool): String = + if (List.isEmpty(fs)) "" else nestFieldsErr2(nestFieldErr(List.at(fs, 0), inTuple), List.tail(fs), inTuple) + +def nestFieldsErr2(err: String, rest: List[String], inTuple: Bool): String = + if (Str.len(err) > 0) err else nestFieldsErr(rest, inTuple) + +def nestFieldErr(f: String, inTuple: Bool): String = + nestFieldErrAt(eqAt(f, 0), f, inTuple) + +def nestFieldErrAt(i: Int, f: String, inTuple: Bool): String = + nestCompErr(trimName(if (i < 0) f else Str.drop(f, i + 3)), inTuple) + +def nestCompErr(c: String, inTuple: Bool): String = + if (asAt(c, 0) >= 0) "nested patterns are not supported here" else if (consAt(c, 0) >= 0) nestConsInErr(c, inTuple) else if (nestSimple(c)) "" else if (isTuplePat(c)) nestTupErr(c, inTuple) else "nested patterns are not supported here" + +def nestTupErr(c: String, inTuple: Bool): String = + if (inTuple) nestFieldsErr(splitComma(Str.slice(c, 1, Str.len(c) - 1)), true) else "nested patterns are not supported here" + +def nestConsInErr(c: String, inTuple: Bool): String = + if (inTuple) "nested patterns are not supported here" else nestConsHeadErr(trimName(Str.take(c, consAt(c, 0))), trimName(Str.drop(c, consAt(c, 0) + 4))) def inferMatchRest(first: Out, rest: List[Arm], scrutTy: String, env: List[(String, Ty)], funs: Ftab, ens: List[En]): Out = if (hasErr(first)) first else if (List.isEmpty(rest)) first else inferMatchArm2(first, inferMatchArm(List.at(rest, 0), scrutTy, env, funs, ens), List.at(rest, 0), List.tail(rest), scrutTy, env, funs, ens) diff --git a/examples/kernel/src/Main.scuzz b/examples/kernel/src/Main.scuzz index 941aae3d..5721258f 100644 --- a/examples/kernel/src/Main.scuzz +++ b/examples/kernel/src/Main.scuzz @@ -266,7 +266,8 @@ def describeSmall(n: Int): String = def describeOptSmall(o: Opt[Int]): String = o match { - case Opt.Some(0 | 1) => "opt01" + case Opt.Some(0) => "opt01" + case Opt.Some(1) => "opt01" case Opt.Some(_) => "optN" case Opt.None => "optNone" } @@ -279,7 +280,7 @@ def keepSome(o: Opt[Int]): String = def describeAsZero(o: Opt[Int]): String = o match { - case Opt.Some(n @ 0) => Str.concat("z:", Str.fromInt(n)) + case Opt.Some(0) => Str.concat("z:", Str.fromInt(0)) case Opt.Some(_) => "n" case Opt.None => "none" } diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index 7438da88..6cfba746 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -341,7 +341,7 @@ def tyckFlow(): Bool = tyckDiff(srcUnBool(), wantUnBool()) && tyckDiff(srcIfMis(), wantIfMis()) && tyckDiff(srcMapHandle(), wantMapHandle()) && tyckDiff(srcJoinMis(), wantJoinMis()) && tyckDiff(srcNope(), wantNope()) && tyckDiff(srcStrUnk(), wantStrUnk()) && tyckDiff(srcStrConcatTy(), wantStrConcatTy()) && tyckDiff(srcSysExit(), wantSysExit()) && unaryNumTypes() def tyckRes(): Bool = - tyckDiff(srcResArity(), wantResArity()) && tyckDiff(srcResDraw(), wantResDraw()) && tyckDiff(srcResLam(), wantResLam()) && tyckDiff(srcResUnk(), wantResUnk()) && tyckDiff(srcJsonUnk(), wantJsonUnk()) && tyckDiff(srcDefUnk(), wantDefUnk()) && tyckDiff(srcDefMis(), wantDefMis()) && tyckDiff(srcQueUnk(), wantQueUnk()) && tyckDiff(srcQueMis(), wantQueMis()) && tyckDiff(srcQueTake(), wantQueTake()) && tyckDiff(srcQuePay(), wantQuePay()) && tyckDiff(srcNetStr(), wantNetStr()) && emptyCtorTypes() && queueRefineTypes() + tyckDiff(srcResArity(), wantResArity()) && tyckDiff(srcResDraw(), wantResDraw()) && tyckDiff(srcResLam(), wantResLam()) && tyckDiff(srcResUnk(), wantResUnk()) && tyckDiff(srcJsonUnk(), wantJsonUnk()) && tyckDiff(srcDefUnk(), wantDefUnk()) && tyckDiff(srcDefMis(), wantDefMis()) && tyckDiff(srcQueUnk(), wantQueUnk()) && tyckDiff(srcQueMis(), wantQueMis()) && tyckDiff(srcQueTake(), wantQueTake()) && tyckDiff(srcQuePay(), wantQuePay()) && tyckDiff(srcNetStr(), wantNetStr()) && emptyCtorTypes() && queueRefineTypes() && nestedPatTypes() def rejects(src: String, msg: String): Bool = Str.contains(Check.check(src), msg) @@ -1020,6 +1020,66 @@ def value(r: Result[Problem, Int]): Int = r match { def unaryNumTypes(): Bool = Check.check("def right(): Float = -1.5") == "[]" && Check.check("def right(n: Float): Float = -n") == "[]" && Check.check("def right(): Int = -1") == "[]" && rejects("def wrong(): Bool = -true", "expected Int or Float, got Bool") +def nestedPatTypes(): Bool = + rejects("""enum Inner: + case Good(v: Int) +enum Outer: + case Wrap(a: Inner, b: Int) +def wrong(o: Outer): Int = o match { + case Outer.Wrap(Inner.Good(n), b) => n + case _ => 0 +}""", "nested patterns are not supported here") && rejects("""enum Box2: + case P(p: (Int, Int)) +def wrong(b: Box2): Int = b match { + case Box2.P((n, m)) => n + m + case _ => 0 +}""", "nested patterns are not supported here") && rejects("""def wrong(p: (Result[String, Int], Int)): Int = p match { + case (Result.Ok(n), m) => n + m + case _ => 0 +}""", "nested patterns are not supported here") && rejects("""def wrong(p: (List[Int], Int)): Int = p match { + case (x :: _, n) => x + n + case _ => 0 +}""", "nested patterns are not supported here") && rejects("""enum Opt: + case Some(value: Int) + case None +def wrong(o: Opt): Int = o match { + case Opt.Some(n @ 0) => n + case _ => 0 +}""", "nested patterns are not supported here") && rejects("""enum Opt: + case Some(value: Int) + case None +enum W: + case V(o: Opt, n: Int) +def wrong(w: W): Int = w match { + case W.V(Opt.None, _) => 100 + case _ => 0 +}""", "nested patterns are not supported here") && Check.check("""enum W: + case V(n: Int, m: Int) +def right(w: W): Int = w match { + case W.V(1, _) => 1 + case _ => 0 +}""") == "[]" && Check.check("""enum BoxL: + case L(xs: List[Int]) +def right(b: BoxL): Int = b match { + case BoxL.L(x :: _) => x + case BoxL.L(_) => 0 +}""") == "[]" && Check.check("""enum Opt: + case Some(value: Int) + case None +def right(o: Opt): Int = o match { + case Opt.Some(0) => 100 + case Opt.Some(n) => n + case Opt.None => 0 - 1 +}""") == "[]" && Check.check("""def right(p: ((Int, Int), Int)): Int = p match { + case ((1, _), n) => n + case ((a, b), n) => a + b + n +}""") == "[]" && rejects("""enum BoxL: + case L(xs: List[Int]) +def wrong(b: BoxL): Int = b match { + case BoxL.L([]) => 1 + case _ => 0 +}""", "nested patterns are not supported here") + def queueRefineTypes(): Bool = Check.check("""@main def main: IO[Unit] = for { From 6ac270efb04b5e0d611390dc2329c62dfa81c16e Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 16 Sep 2026 19:24:06 -0400 Subject: [PATCH 08/23] Guard application of lambdas with unresolved parameter types. An unannotated lambda bound its parameter to the letter A, which unifies with any type. f = (n => n + 1) then f("s") passed check and crashed at runtime. inferFunApply1 now rejects applying an env-bound function whose parameter type is an unresolved param letter to a concrete argument. The message asks for an annotation. Loose arguments still pass, so generic def bodies are unchanged. An inline lambda in apply position now infers with the argument type as its expected type: (n => n + 1)(1) is Int, and (n => n + 1)("s") fails with the arithmetic error. This also keeps the verify rewriter sound: its generated pred.apply(reqv) applications now check the pred against the payload type with no annotation needed. The tyck oracle covers the crash case, annotated lambdas, the monomorphic identity rejection, and inline apply. LLVM IR fixed-point converges. The kernel suite and the io fuzz replay pass. --- docs/gaps.md | 2 +- examples/compiler/src/Check.scuzz | 22 ++++++++++++++++++++-- examples/tyck/src/Main.scuzz | 15 ++++++++++++++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/docs/gaps.md b/docs/gaps.md index df5df701..1c947f69 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -37,7 +37,7 @@ Do not add library publishing, git or registry deps, or `scuzz add`. Path deps s Resolve these gaps when they prevent ordinary language use. -1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions. A payload seen only through a lambda parameter does not pin. Param letters (`A`/`E`) still unify. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest. A nested constructor, tuple, as, or cons pattern in a constructor field or tuple component fails check. A bare enum case, float literal, or list literal in those positions also fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. +1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions. A payload seen only through a lambda parameter does not pin. Applying an env-bound lambda with an unresolved parameter letter to a concrete argument fails check. A loose function type can still escape through an expected function type. Param letters (`A`/`E`) still unify elsewhere in `Type.eq`. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest. A nested constructor, tuple, as, or cons pattern in a constructor field or tuple component fails check. A bare enum case, float literal, or list literal in those positions also fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. 2. **Compile-time performance** — `scuzz check examples/compiler` is 16 s. A cold `scuzz build examples/tyck` is 1 m 10 s. Remaining cost: RC retain/release churn and `sz_list_concat` in string building. Coverage still parses a compiled graph that differs from live. diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index a77f1aaf..d927b569 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -612,7 +612,7 @@ def inferFunApply(ty: Ty, args: List[Expr], env: List[(String, Ty)], funs: Ftab, if (List.isEmpty(args)) bad("apply expects 1 arg", span) else if (List.len(args) == 1) inferFunApply1(ty, infer(List.at(args, 0), env, funs, ens), spanPick(exprSpan(env, List.at(args, 0)), span), ens) else inferFunApply1(ty, inferTuple(args, env, funs, ens), span, ens) def inferFunApply1(ty: Ty, o: Out, span: (String, Int), ens: List[En]): Out = - if (hasErr(o)) o else if (tyEqEn(tyStr(o), Type.show(Type.funArg(ty)), ens)) okTy(Type.funRet(ty)) else argMismatch("apply", Type.show(Type.funArg(ty)), tyStr(o), span) + if (hasErr(o)) o else if (Type.isParam(Type.funArg(ty)) && !isLoose(tyStr(o))) bad("lambda parameter type is unresolved; annotate the parameter", span) else if (tyEqEn(tyStr(o), Type.show(Type.funArg(ty)), ens)) okTy(Type.funRet(ty)) else argMismatch("apply", Type.show(Type.funArg(ty)), tyStr(o), span) def elemOf(t: String): String = elemOf2(brackAt(t, 0), t) @@ -1430,7 +1430,25 @@ def requirePredOk(ty: String): Bool = ty == "Bool" || isLoose(ty) || isIo(ty) && (ioOk(ty) == "Bool" || isLoose(ioOk(ty))) || isFunTy(ty) && (funRetOf(ty) == "Bool" || isLoose(funRetOf(ty))) def inferApply(recv: Expr, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = - inferApply2(infer(recv, env, funs, ens), args, env, funs, ens, e) + recv match { + case Expr.ELam(_, _, _) => inferApplyLam(recv, args, env, funs, ens, e) + case _ => inferApply2(infer(recv, env, funs, ens), args, env, funs, ens, e) + } + +def inferApplyLam(lam: Expr, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = + if (List.len(args) != 1) bad("apply expects 1 arg", exprSpan(env, e)) else inferApplyLam2(infer(stripNamed(List.at(args, 0)), env, funs, ens), lam, env, funs, ens, spanPick(exprSpan(env, stripNamed(List.at(args, 0))), exprSpan(env, e))) + +def inferApplyLam2(a: Out, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], asp: (String, Int)): Out = + if (hasErr(a)) a else inferApplyLam3(inferApplyExpected(lam, tyStr(a), env, funs, ens), tyStr(a), env, ens, asp) + +def inferApplyExpected(lam: Expr, argTy: String, env: List[(String, Ty)], funs: Ftab, ens: List[En]): Out = + lam match { + case Expr.ELam(p, ann, body) => if (ann == "") inferExpected(lam, Str.concat(argTy, " => B"), env, funs, ens) else infer(lam, env, funs, ens) + case _ => infer(lam, env, funs, ens) + } + +def inferApplyLam3(lo: Out, argTy: String, env: List[(String, Ty)], ens: List[En], asp: (String, Int)): Out = + if (hasErr(lo)) lo else if (tyEqEn(argTy, Type.show(Type.funArg(lo.ty)), ens)) okTy(Type.funRet(lo.ty)) else argMismatch("apply", Type.show(Type.funArg(lo.ty)), argTy, asp) def inferApply2(o: Out, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = if (hasErr(o)) o else if (Type.isFun(o.ty) || isLoose(tyStr(o))) inferFunApply(o.ty, args, env, funs, ens, exprSpan(env, e)) else bad(Str.concat("apply needs a function, got ", tyStr(o)), exprSpan(env, e)) diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index 6cfba746..bab5d611 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -341,7 +341,7 @@ def tyckFlow(): Bool = tyckDiff(srcUnBool(), wantUnBool()) && tyckDiff(srcIfMis(), wantIfMis()) && tyckDiff(srcMapHandle(), wantMapHandle()) && tyckDiff(srcJoinMis(), wantJoinMis()) && tyckDiff(srcNope(), wantNope()) && tyckDiff(srcStrUnk(), wantStrUnk()) && tyckDiff(srcStrConcatTy(), wantStrConcatTy()) && tyckDiff(srcSysExit(), wantSysExit()) && unaryNumTypes() def tyckRes(): Bool = - tyckDiff(srcResArity(), wantResArity()) && tyckDiff(srcResDraw(), wantResDraw()) && tyckDiff(srcResLam(), wantResLam()) && tyckDiff(srcResUnk(), wantResUnk()) && tyckDiff(srcJsonUnk(), wantJsonUnk()) && tyckDiff(srcDefUnk(), wantDefUnk()) && tyckDiff(srcDefMis(), wantDefMis()) && tyckDiff(srcQueUnk(), wantQueUnk()) && tyckDiff(srcQueMis(), wantQueMis()) && tyckDiff(srcQueTake(), wantQueTake()) && tyckDiff(srcQuePay(), wantQuePay()) && tyckDiff(srcNetStr(), wantNetStr()) && emptyCtorTypes() && queueRefineTypes() && nestedPatTypes() + tyckDiff(srcResArity(), wantResArity()) && tyckDiff(srcResDraw(), wantResDraw()) && tyckDiff(srcResLam(), wantResLam()) && tyckDiff(srcResUnk(), wantResUnk()) && tyckDiff(srcJsonUnk(), wantJsonUnk()) && tyckDiff(srcDefUnk(), wantDefUnk()) && tyckDiff(srcDefMis(), wantDefMis()) && tyckDiff(srcQueUnk(), wantQueUnk()) && tyckDiff(srcQueMis(), wantQueMis()) && tyckDiff(srcQueTake(), wantQueTake()) && tyckDiff(srcQuePay(), wantQuePay()) && tyckDiff(srcNetStr(), wantNetStr()) && emptyCtorTypes() && queueRefineTypes() && nestedPatTypes() && lambdaApplyTypes() def rejects(src: String, msg: String): Bool = Str.contains(Check.check(src), msg) @@ -1020,6 +1020,19 @@ def value(r: Result[Problem, Int]): Int = r match { def unaryNumTypes(): Bool = Check.check("def right(): Float = -1.5") == "[]" && Check.check("def right(n: Float): Float = -n") == "[]" && Check.check("def right(): Int = -1") == "[]" && rejects("def wrong(): Bool = -true", "expected Int or Float, got Bool") +def lambdaApplyTypes(): Bool = + rejects("@main def main: IO[Unit] =\n for {\n f = (n => n + 1)\n _ <- IO.println(Str.fromInt(f(\"s\")))\n } yield ()\n", "lambda parameter type is unresolved") && Check.check("""@main def main: IO[Unit] = + for { + f = ((n: Int) => n + 1) + _ <- IO.println(Str.fromInt(f(1))) + } yield () +""") == "[]" && rejects("@main def main: IO[Unit] =\n for {\n f = ((n: Int) => n + 1)\n _ <- IO.println(Str.fromInt(f(\"s\")))\n } yield ()\n", "apply arg type mismatch: expected Int, got String") && rejects("""@main def main: IO[Unit] = + for { + f = (n => n) + _ <- IO.println(Str.fromInt(f(1))) + } yield () +""", "lambda parameter type is unresolved") && Check.check("@main def main: IO[Unit] =\n for {\n f = ((n: String) => n)\n _ <- IO.println(f(\"s\"))\n } yield ()\n") == "[]" && Check.check("def right(): Int = (n => n + 1)(1)") == "[]" && rejects("def wrong(): Int = (n => n + 1)(\"s\")", "arithmetic needs Int or Float") + def nestedPatTypes(): Bool = rejects("""enum Inner: case Good(v: Int) From ef08b15020b64f6ff05294c610f1929a7bc7c8df Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 16 Sep 2026 20:38:19 -0400 Subject: [PATCH 09/23] Pin Queue and Deferred payloads through lambda parameters. refineEnv pinned a bare handle only when an offer or complete named an env var directly. A payload seen only through a lambda parameter did not pin: IO.foreach([q], x => Queue.offer(x, 1)) left q bare, so a later take kept the loose type and unified with anything. refineEnv now detects iteration calls that bind a lambda parameter from a collection element: IO.foreach and IO.foreachDiscard in call form, and foreach, map, flatMap, and filter in method form. When the element type is a bare handle, the lambda parameter binds in the refinement env. If the body pins the parameter, the pinned payload propagates back to the collection source. A list literal pins each bare-handle element var. A list var pins its element type. The tyck oracle covers consistent and mismatched takes after a lambda-param offer, for Queue and Deferred, in both call and method forms. LLVM IR fixed-point converges. The kernel suite and the kits slice pass with the new compiler. --- docs/gaps.md | 2 +- examples/compiler/src/Check.scuzz | 56 +++++++++++++++++++++++++++++-- examples/tyck/src/Main.scuzz | 47 +++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 4 deletions(-) diff --git a/docs/gaps.md b/docs/gaps.md index 1c947f69..c7d616d6 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -37,7 +37,7 @@ Do not add library publishing, git or registry deps, or `scuzz add`. Path deps s Resolve these gaps when they prevent ordinary language use. -1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions. A payload seen only through a lambda parameter does not pin. Applying an env-bound lambda with an unresolved parameter letter to a concrete argument fails check. A loose function type can still escape through an expected function type. Param letters (`A`/`E`) still unify elsewhere in `Type.eq`. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest. A nested constructor, tuple, as, or cons pattern in a constructor field or tuple component fails check. A bare enum case, float literal, or list literal in those positions also fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. +1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions and payloads seen through a lambda parameter over a collection. Applying an env-bound lambda with an unresolved parameter letter to a concrete argument fails check. A loose function type can still escape through an expected function type. Param letters (`A`/`E`) still unify elsewhere in `Type.eq`. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest. A nested constructor, tuple, as, or cons pattern in a constructor field or tuple component fails check. A bare enum case, float literal, or list literal in those positions also fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. 2. **Compile-time performance** — `scuzz check examples/compiler` is 16 s. A cold `scuzz build examples/tyck` is 1 m 10 s. Remaining cost: RC retain/release churn and `sz_list_concat` in string building. Coverage still parses a compiled graph that differs from live. diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index d927b569..63513a81 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -888,8 +888,8 @@ def bindFor(d: Bool, name: String, ty: String, env: List[(String, Ty)], ens: Lis def refineEnv(e: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En]): List[(String, Ty)] = e match { - case Expr.ECall(f, args, _) => refineExprs(args, refineOfferCall(f, args, env, funs, ens), funs, ens) - case Expr.EMethod(recv, name, args, _) => refineExprs(args, refineEnv(recv, refineOfferMeth(recv, name, args, env, funs, ens), funs, ens), funs, ens) + case Expr.ECall(f, args, _) => refineExprs(args, refineIterCall(f, args, refineOfferCall(f, args, env, funs, ens), funs, ens), funs, ens) + case Expr.EMethod(recv, name, args, _) => refineExprs(args, refineEnv(recv, refineIterMeth(recv, name, args, refineOfferMeth(recv, name, args, env, funs, ens), funs, ens), funs, ens), funs, ens) case Expr.EAscribe(inner, _, _) => refineEnv(inner, env, funs, ens) case Expr.ENamed(_, inner) => refineEnv(inner, env, funs, ens) case Expr.ELam(_, _, body) => refineEnv(body, env, funs, ens) @@ -933,6 +933,58 @@ def offerCtor(f: String): String = def refineOfferMeth(recv: Expr, name: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En]): List[(String, Ty)] = if (name == "offer") refineOfferRecv(recv, "Queue", args, env, funs, ens) else if (name == "complete") refineOfferRecv(recv, "Deferred", args, env, funs, ens) else env +def refineIterCall(f: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En]): List[(String, Ty)] = + if ((f == "IO.foreach" || f == "IO.foreachDiscard") && List.len(args) == 2) refineIterLam(List.at(args, 0), List.at(args, 1), env, funs, ens) else env + +def refineIterMeth(recv: Expr, name: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En]): List[(String, Ty)] = + if ((name == "foreach" || name == "map" || name == "flatMap" || name == "filter") && List.len(args) == 1) refineIterLam(recv, List.at(args, 0), env, funs, ens) else env + +def refineIterLam(xs: Expr, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En]): List[(String, Ty)] = + lam match { + case Expr.ELam(p, ann, body) => refineIterLam2(xs, p, body, iterParamTy(ann, iterElemTy(xs, env, funs, ens)), env, funs, ens) + case _ => env + } + +def iterElemTy(xs: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En]): String = + iterElemTy2(infer(xs, env, funs, ens)) + +def iterElemTy2(o: Out): String = + if (hasErr(o) || !Str.startsWith(tyStr(o), "List[")) "" else elemOf(tyStr(o)) + +def iterParamTy(ann: String, el: String): String = + if (ann != "") ann else el + +def refineIterLam2(xs: Expr, p: String, body: Expr, pt: String, env: List[(String, Ty)], funs: Ftab, ens: List[En]): List[(String, Ty)] = + if (p == "" || p == "_") env else if (!isBareHandle(pt, "Queue") && !isBareHandle(pt, "Deferred")) env else refineIterPin(xs, p, pt, refineEnv(body, envBind(p, pt, env), funs, ens)) + +def refineIterPin(xs: Expr, p: String, pt: String, env: List[(String, Ty)]): List[(String, Ty)] = + refineIterPin2(xs, p, pt, Type.show(lookupEnv(env, p)), List.filter(env, e => e._1 != p)) + +def refineIterPin2(xs: Expr, p: String, pt: String, pinned: String, env: List[(String, Ty)]): List[(String, Ty)] = + if (pinned == "" || pinned == pt) env else pinIterSource(xs, pinned, env) + +def pinIterSource(xs: Expr, pay: String, env: List[(String, Ty)]): List[(String, Ty)] = + xs match { + case Expr.EVar(n, _) => pinIterVar(n, pay, env) + case Expr.EList(es) => pinIterElems(es, pay, env) + case _ => env + } + +def pinIterVar(n: String, pay: String, env: List[(String, Ty)]): List[(String, Ty)] = + if (isBareHandle(iterElemOf(getTy(env, n)), Type.headNameStr(pay))) envBind(n, Str.concat("List[", Str.concat(pay, "]")), env) else env + +def iterElemOf(ty: String): String = + if (Str.startsWith(ty, "List[")) elemOf(ty) else "" + +def pinIterElems(es: List[Expr], pay: String, env: List[(String, Ty)]): List[(String, Ty)] = + if (List.isEmpty(es)) env else pinIterElems(List.tail(es), pay, pinIterElem(List.at(es, 0), pay, env)) + +def pinIterElem(e: Expr, pay: String, env: List[(String, Ty)]): List[(String, Ty)] = + e match { + case Expr.EVar(n, _) => if (isBareHandle(getTy(env, n), Type.headNameStr(pay))) envBind(n, pay, env) else env + case _ => env + } + def refineOfferArgs(ctor: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En]): List[(String, Ty)] = if (List.len(args) < 2) env else refineHandlePay(ctor, List.at(args, 0), List.at(args, 1), env, funs, ens) diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index bab5d611..37596ad1 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -341,7 +341,7 @@ def tyckFlow(): Bool = tyckDiff(srcUnBool(), wantUnBool()) && tyckDiff(srcIfMis(), wantIfMis()) && tyckDiff(srcMapHandle(), wantMapHandle()) && tyckDiff(srcJoinMis(), wantJoinMis()) && tyckDiff(srcNope(), wantNope()) && tyckDiff(srcStrUnk(), wantStrUnk()) && tyckDiff(srcStrConcatTy(), wantStrConcatTy()) && tyckDiff(srcSysExit(), wantSysExit()) && unaryNumTypes() def tyckRes(): Bool = - tyckDiff(srcResArity(), wantResArity()) && tyckDiff(srcResDraw(), wantResDraw()) && tyckDiff(srcResLam(), wantResLam()) && tyckDiff(srcResUnk(), wantResUnk()) && tyckDiff(srcJsonUnk(), wantJsonUnk()) && tyckDiff(srcDefUnk(), wantDefUnk()) && tyckDiff(srcDefMis(), wantDefMis()) && tyckDiff(srcQueUnk(), wantQueUnk()) && tyckDiff(srcQueMis(), wantQueMis()) && tyckDiff(srcQueTake(), wantQueTake()) && tyckDiff(srcQuePay(), wantQuePay()) && tyckDiff(srcNetStr(), wantNetStr()) && emptyCtorTypes() && queueRefineTypes() && nestedPatTypes() && lambdaApplyTypes() + tyckDiff(srcResArity(), wantResArity()) && tyckDiff(srcResDraw(), wantResDraw()) && tyckDiff(srcResLam(), wantResLam()) && tyckDiff(srcResUnk(), wantResUnk()) && tyckDiff(srcJsonUnk(), wantJsonUnk()) && tyckDiff(srcDefUnk(), wantDefUnk()) && tyckDiff(srcDefMis(), wantDefMis()) && tyckDiff(srcQueUnk(), wantQueUnk()) && tyckDiff(srcQueMis(), wantQueMis()) && tyckDiff(srcQueTake(), wantQueTake()) && tyckDiff(srcQuePay(), wantQuePay()) && tyckDiff(srcNetStr(), wantNetStr()) && emptyCtorTypes() && queueRefineTypes() && queueLambdaPinTypes() && nestedPatTypes() && lambdaApplyTypes() def rejects(src: String, msg: String): Bool = Str.contains(Check.check(src), msg) @@ -1131,6 +1131,51 @@ def queueRefineTypes(): Bool = } yield () """) == "[]" +def queueLambdaPinTypes(): Bool = + Check.check("""@main def main: IO[Unit] = + for { + q <- Queue.unbounded() + _ <- IO.foreach([q], x => Queue.offer(x, 1)) + n <- Queue.take(q) + _ <- IO.println(Str.fromInt(n)) + } yield () +""") == "[]" && rejects("""@main def main: IO[Unit] = + for { + q <- Queue.unbounded() + _ <- IO.foreach([q], x => Queue.offer(x, 1)) + s <- Queue.take(q) + _ <- IO.println(s) + } yield () +""", "expected String, got Int") && Check.check("""@main def main: IO[Unit] = + for { + q <- Queue.unbounded() + _ <- [q].map(x => Queue.offer(x, 1)) + n <- Queue.take(q) + _ <- IO.println(Str.fromInt(n)) + } yield () +""") == "[]" && rejects("""@main def main: IO[Unit] = + for { + q <- Queue.unbounded() + _ <- [q].map(x => Queue.offer(x, 1)) + s <- Queue.take(q) + _ <- IO.println(s) + } yield () +""", "expected String, got Int") && Check.check("""@main def main: IO[Unit] = + for { + d <- Deferred.empty() + _ <- IO.foreachDiscard([d], x => Deferred.complete(x, 1)) + n <- Deferred.get(d) + _ <- IO.println(Str.fromInt(n)) + } yield () +""") == "[]" && rejects("""@main def main: IO[Unit] = + for { + d <- Deferred.empty() + _ <- IO.foreach([d], x => Deferred.complete(x, 1)) + s <- Deferred.get(d) + _ <- IO.println(s) + } yield () +""", "expected String, got Int") + def emptyCtorTypes(): Bool = Check.check("def right(): Map[String, Int] = Map.set(Map.empty(), \"a\", 1)") == "[]" && rejects("def wrong(): Map[String, String] = Map.set(Map.empty(), \"a\", 1)", "does not match declared") && Check.check("def right(m: Map[String, Int]): Map[String, Int] = Map.set(m, \"b\", 2)") == "[]" && rejects("def wrong(m: Map[String, Int]): Map[String, Int] = Map.set(m, 1, 2)", "Map.set arg type mismatch: expected String, got Int") && rejects("def wrong(m: Map[String, Int]): Map[String, Int] = Map.set(m, \"b\", \"x\")", "Map.set arg type mismatch: expected Int, got String") && Check.check("def right(m: Map[String, Int]): Option[Int] = Map.get(m, \"k\")") == "[]" && rejects("def wrong(m: Map[String, Int]): Option[Int] = Map.get(m, 1)", "Map.get arg type mismatch: expected String, got Int") && Check.check("def right(m: Map[String, Int]): Int = Map.getOrElse(m, \"k\", 0)") == "[]" && rejects("def wrong(m: Map[String, Int]): Int = Map.getOrElse(m, \"k\", \"x\")", "Map.getOrElse arg type mismatch: expected Int, got String") && Check.check("def right(): Set[Int] = Set.add(Set.empty(), 1)") == "[]" && rejects("def wrong(): Set[String] = Set.add(Set.empty(), 1)", "does not match declared") && rejects("def wrong(s: Set[Int]): Bool = Set.contains(s, \"x\")", "Set.contains arg type mismatch: expected Int, got String") && rejects("def wrong(a: Set[Int], b: Set[String]): Set[Int] = Set.union(a, b)", "Set.union arg type mismatch: expected Set[Int], got Set[String]") && Check.check("def right(xs: List[Int]): List[Int] = List.cons(1, xs)") == "[]" && rejects("def wrong(xs: List[Int]): List[String] = List.cons(\"s\", xs)", "List.cons arg type mismatch: expected List[String], got List[Int]") && Check.check("def right(): List[Int] = List.cons(1, List.empty())") == "[]" From befd951527729f5c19bb866ead865202c4e13c1e Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 16 Sep 2026 21:11:14 -0400 Subject: [PATCH 10/23] Bind lambda parameters from expected function types. A lambda argument checked against a concrete function type inferred without the expected type. The unannotated lambda kept the loose A => A, which unified with any function type: apply(n => n) passed against f: Int => String, and f(1) returned Int where String was declared. The same escape held in def-body position. zipCheckGo now infers each argument with inferExpected, matching the generic call path. checkDefWhere infers the body with inferExpected against the declared return type. An unannotated lambda binds its parameter from the expected argument type, so apply(n => n) fails with expected Int => String, got Int => Int, and def wrong(): Int => String = n => n fails against the declared type. Three oracles moved to the more precise lambda annotation message: the mismatched annotation now fails at the parameter instead of the whole body. New expectedLamTypes oracle covers call-form and def-body positions, annotated and unannotated, first and last arguments. LLVM IR fixed-point converges. The kernel suite and the kits slice pass with the new compiler. --- docs/gaps.md | 2 +- examples/compiler/src/Check.scuzz | 4 ++-- examples/tyck/src/Main.scuzz | 30 +++++++++++++++++++++++------- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/docs/gaps.md b/docs/gaps.md index c7d616d6..df7e850c 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -37,7 +37,7 @@ Do not add library publishing, git or registry deps, or `scuzz add`. Path deps s Resolve these gaps when they prevent ordinary language use. -1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions and payloads seen through a lambda parameter over a collection. Applying an env-bound lambda with an unresolved parameter letter to a concrete argument fails check. A loose function type can still escape through an expected function type. Param letters (`A`/`E`) still unify elsewhere in `Type.eq`. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest. A nested constructor, tuple, as, or cons pattern in a constructor field or tuple component fails check. A bare enum case, float literal, or list literal in those positions also fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. +1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions and payloads seen through a lambda parameter over a collection. Applying an env-bound lambda with an unresolved parameter letter to a concrete argument fails check. An unannotated lambda in argument or def-body position binds its parameter from the expected function type, so a loose lambda can no longer escape through an expected function type. Param letters (`A`/`E`) still unify elsewhere in `Type.eq`. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest. A nested constructor, tuple, as, or cons pattern in a constructor field or tuple component fails check. A bare enum case, float literal, or list literal in those positions also fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. 2. **Compile-time performance** — `scuzz check examples/compiler` is 16 s. A cold `scuzz build examples/tyck` is 1 m 10 s. Remaining cost: RC retain/release churn and `sz_list_concat` in string building. Coverage still parses a compiled graph that differs from live. diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index 63513a81..a147982e 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -293,7 +293,7 @@ def zipCheckArg(callee: String, w: String, want: List[String], arg: Expr, args: zipCheckGo(callee, w, want, arg, args, env, funs, ens, span, acc, spanPick(exprSpan(env, stripNamed(arg)), span)) def zipCheckGo(callee: String, w: String, want: List[String], arg: Expr, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int), acc: String, asp: (String, Int)): Out = - if (List.isEmpty(args)) zipCheckLast(infer(stripNamed(arg), env, funs, ens), callee, w, asp, acc, ens) else zipCheck2(callee, w, infer(stripNamed(arg), env, funs, ens), want, args, env, funs, ens, span, acc, asp) + if (List.isEmpty(args)) zipCheckLast(inferExpected(stripNamed(arg), w, env, funs, ens), callee, w, asp, acc, ens) else zipCheck2(callee, w, inferExpected(stripNamed(arg), w, env, funs, ens), want, args, env, funs, ens, span, acc, asp) def zipCheckLast(o: Out, callee: String, w: String, span: (String, Int), acc: String, ens: List[En]): Out = if (hasErr(o)) o else if (tyEqEn(tyStr(o), w, ens)) ok(if (acc == "") tyStr(o) else acc) else argMismatch(callee, w, tyStr(o), span) @@ -1693,7 +1693,7 @@ def checkDef(d: Fun, funs: Ftab, ens: List[En]): Out = } def checkDefWhere(name: String, ret: String, body: Expr, ps: List[Param], funs: Ftab, ens: List[En], mod: String, off: Int, wo: Out): Out = - if (hasErr(wo)) checkDefWrap(name, ret, body, wo, (mod, off), ens) else checkDefWrap(name, ret, body, infer(body, bindParams(ps, envSelfMod(mod)), funs, ens), (mod, off), ens) + if (hasErr(wo)) checkDefWrap(name, ret, body, wo, (mod, off), ens) else checkDefWrap(name, ret, body, inferExpected(body, ret, bindParams(ps, envSelfMod(mod)), funs, ens), (mod, off), ens) def checkWheres(ps: List[Param], env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = if (List.isEmpty(ps)) ok("Bool") else checkWhereHd(List.at(ps, 0), List.tail(ps), env, funs, ens, span) diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index 37596ad1..118179e2 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -341,7 +341,7 @@ def tyckFlow(): Bool = tyckDiff(srcUnBool(), wantUnBool()) && tyckDiff(srcIfMis(), wantIfMis()) && tyckDiff(srcMapHandle(), wantMapHandle()) && tyckDiff(srcJoinMis(), wantJoinMis()) && tyckDiff(srcNope(), wantNope()) && tyckDiff(srcStrUnk(), wantStrUnk()) && tyckDiff(srcStrConcatTy(), wantStrConcatTy()) && tyckDiff(srcSysExit(), wantSysExit()) && unaryNumTypes() def tyckRes(): Bool = - tyckDiff(srcResArity(), wantResArity()) && tyckDiff(srcResDraw(), wantResDraw()) && tyckDiff(srcResLam(), wantResLam()) && tyckDiff(srcResUnk(), wantResUnk()) && tyckDiff(srcJsonUnk(), wantJsonUnk()) && tyckDiff(srcDefUnk(), wantDefUnk()) && tyckDiff(srcDefMis(), wantDefMis()) && tyckDiff(srcQueUnk(), wantQueUnk()) && tyckDiff(srcQueMis(), wantQueMis()) && tyckDiff(srcQueTake(), wantQueTake()) && tyckDiff(srcQuePay(), wantQuePay()) && tyckDiff(srcNetStr(), wantNetStr()) && emptyCtorTypes() && queueRefineTypes() && queueLambdaPinTypes() && nestedPatTypes() && lambdaApplyTypes() + tyckDiff(srcResArity(), wantResArity()) && tyckDiff(srcResDraw(), wantResDraw()) && tyckDiff(srcResLam(), wantResLam()) && tyckDiff(srcResUnk(), wantResUnk()) && tyckDiff(srcJsonUnk(), wantJsonUnk()) && tyckDiff(srcDefUnk(), wantDefUnk()) && tyckDiff(srcDefMis(), wantDefMis()) && tyckDiff(srcQueUnk(), wantQueUnk()) && tyckDiff(srcQueMis(), wantQueMis()) && tyckDiff(srcQueTake(), wantQueTake()) && tyckDiff(srcQuePay(), wantQuePay()) && tyckDiff(srcNetStr(), wantNetStr()) && emptyCtorTypes() && queueRefineTypes() && queueLambdaPinTypes() && nestedPatTypes() && lambdaApplyTypes() && expectedLamTypes() def rejects(src: String, msg: String): Bool = Str.contains(Check.check(src), msg) @@ -514,7 +514,7 @@ def tyckLam(): Bool = tyckDiff(srcLamAnn(), wantLamAnn()) && tyckDiff(srcLamMis(), wantLamMis()) && tyckDiff(srcLamArg(), wantLamArg()) && tyckDiff(srcFunBody(), wantFunBody()) && wantLamMis() != "[]" && wantLamArg() != "[]" && wantFunBody() != "[]" && funArrowTypes() def funArrowTypes(): Bool = - Type.funFrom("Int => String" :: [], "String") == "(Int => String) => String" && Type.funFrom("Int" :: [], "String") == "Int => String" && Type.funFrom("Int" :: "String" :: [], "Bool") == "(Int, String) => Bool" && Type.substStr("A => A", "A", "Int => String") == "(Int => String) => Int => String" && Check.check(srcFunArrowOk()) == "[]" && rejects(srcFunArrowMis(), "does not match declared") && rejects(srcFunArrowApply(), "arg type mismatch") && rejects(srcFunListApply(), "apply needs a function") && Check.check(srcFunSubstOk()) == "[]" && rejects(srcFunSubstMis(), "does not match declared") && Check.check(srcFunSubstId()) == "[]" && Check.check(srcFunTupFieldOk()) == "[]" && rejects(srcFunTupFieldMis(), "tuple has no field") && rejects(srcFunTupFieldKeep(), "tuple has no field") && funKitHeadTypes() + Type.funFrom("Int => String" :: [], "String") == "(Int => String) => String" && Type.funFrom("Int" :: [], "String") == "Int => String" && Type.funFrom("Int" :: "String" :: [], "Bool") == "(Int, String) => Bool" && Type.substStr("A => A", "A", "Int => String") == "(Int => String) => Int => String" && Check.check(srcFunArrowOk()) == "[]" && rejects(srcFunArrowMis(), "lambda arg type mismatch: expected Int, got Int => String") && rejects(srcFunArrowApply(), "arg type mismatch") && rejects(srcFunListApply(), "apply needs a function") && Check.check(srcFunSubstOk()) == "[]" && rejects(srcFunSubstMis(), "does not match declared") && Check.check(srcFunSubstId()) == "[]" && Check.check(srcFunTupFieldOk()) == "[]" && rejects(srcFunTupFieldMis(), "tuple has no field") && rejects(srcFunTupFieldKeep(), "tuple has no field") && funKitHeadTypes() def srcFunArrowOk(): String = """def right(): (Int => String) => String = @@ -669,10 +669,10 @@ def right(): E => Int = (x: E) => 0 """) == "[]" && rejects("""enum E: case Ok def wrong(): E => Int = (x: Int) => x -""", "does not match declared") +""", "lambda arg type mismatch: expected E, got Int") def aliasTypes(): Bool = - Check.check(srcAliasListOk()) == "[]" && rejects(srcAliasListMis(), "does not match declared") && Check.check(srcAliasBoxOk()) == "[]" && Check.check(srcAliasFunOk()) == "[]" && rejects(srcAliasFunMis(), "does not match declared") && Check.check(srcAliasId()) == "[]" + Check.check(srcAliasListOk()) == "[]" && rejects(srcAliasListMis(), "does not match declared") && Check.check(srcAliasBoxOk()) == "[]" && Check.check(srcAliasFunOk()) == "[]" && rejects(srcAliasFunMis(), "lambda arg type mismatch: expected Int, got String") && Check.check(srcAliasId()) == "[]" def srcAliasListOk(): String = """type UserId = Int @@ -749,13 +749,13 @@ def wrong(): Option[E] = Option.Some(1) """)) else if (!rejects("""enum E: case Ok def wrong(): E => Int = (x: Int) => x -""", "does not match declared")) Str.concat("funEMis ", Check.check("""enum E: +""", "lambda arg type mismatch: expected E, got Int")) Str.concat("funEMis ", Check.check("""enum E: case Ok def wrong(): E => Int = (x: Int) => x """)) else "namedLetter-other" def dumpAliasTypes(): String = - if (Check.check(srcAliasListOk()) != "[]") Str.concat("aliasListOk ", Check.check(srcAliasListOk())) else if (!rejects(srcAliasListMis(), "does not match declared")) Str.concat("aliasListMis ", Check.check(srcAliasListMis())) else if (Check.check(srcAliasBoxOk()) != "[]") Str.concat("aliasBoxOk ", Check.check(srcAliasBoxOk())) else if (Check.check(srcAliasFunOk()) != "[]") Str.concat("aliasFunOk ", Check.check(srcAliasFunOk())) else if (!rejects(srcAliasFunMis(), "does not match declared")) Str.concat("aliasFunMis ", Check.check(srcAliasFunMis())) else if (Check.check(srcAliasId()) != "[]") Str.concat("aliasId ", Check.check(srcAliasId())) else "alias-other" + if (Check.check(srcAliasListOk()) != "[]") Str.concat("aliasListOk ", Check.check(srcAliasListOk())) else if (!rejects(srcAliasListMis(), "does not match declared")) Str.concat("aliasListMis ", Check.check(srcAliasListMis())) else if (Check.check(srcAliasBoxOk()) != "[]") Str.concat("aliasBoxOk ", Check.check(srcAliasBoxOk())) else if (Check.check(srcAliasFunOk()) != "[]") Str.concat("aliasFunOk ", Check.check(srcAliasFunOk())) else if (!rejects(srcAliasFunMis(), "lambda arg type mismatch: expected Int, got String")) Str.concat("aliasFunMis ", Check.check(srcAliasFunMis())) else if (Check.check(srcAliasId()) != "[]") Str.concat("aliasId ", Check.check(srcAliasId())) else "alias-other" def tyckRecordBind(): Bool = Check.check("""record Box(value: Int) @@ -818,7 +818,7 @@ def dumpTyckLam(): String = if (!funArrowTypes()) dumpFunArrow() else "tyckLam" def dumpFunArrow(): String = - if (Type.funFrom("Int => String" :: [], "String") != "(Int => String) => String") "funFrom" else if (Type.substStr("A => A", "A", "Int => String") != "(Int => String) => Int => String") "substFun" else if (Check.check(srcFunArrowOk()) != "[]") Str.concat("funArrowOk ", Check.check(srcFunArrowOk())) else if (!rejects(srcFunArrowMis(), "does not match declared")) Str.concat("funArrowMis ", Check.check(srcFunArrowMis())) else if (!rejects(srcFunArrowApply(), "arg type mismatch")) Str.concat("funArrowApply ", Check.check(srcFunArrowApply())) else if (!rejects(srcFunListApply(), "apply needs a function")) Str.concat("funListApply ", Check.check(srcFunListApply())) else if (Check.check(srcFunSubstOk()) != "[]") Str.concat("funSubstOk ", Check.check(srcFunSubstOk())) else if (!rejects(srcFunSubstMis(), "does not match declared")) Str.concat("funSubstMis ", Check.check(srcFunSubstMis())) else if (Check.check(srcFunSubstId()) != "[]") Str.concat("funSubstId ", Check.check(srcFunSubstId())) else if (Check.check(srcFunTupFieldOk()) != "[]") Str.concat("funTupFieldOk ", Check.check(srcFunTupFieldOk())) else if (!rejects(srcFunTupFieldMis(), "tuple has no field")) Str.concat("funTupFieldMis ", Check.check(srcFunTupFieldMis())) else if (!rejects(srcFunTupFieldKeep(), "tuple has no field")) Str.concat("funTupFieldKeep ", Check.check(srcFunTupFieldKeep())) else if (!funKitHeadTypes()) dumpFunKitHead() else "funArrow-other" + if (Type.funFrom("Int => String" :: [], "String") != "(Int => String) => String") "funFrom" else if (Type.substStr("A => A", "A", "Int => String") != "(Int => String) => Int => String") "substFun" else if (Check.check(srcFunArrowOk()) != "[]") Str.concat("funArrowOk ", Check.check(srcFunArrowOk())) else if (!rejects(srcFunArrowMis(), "lambda arg type mismatch: expected Int, got Int => String")) Str.concat("funArrowMis ", Check.check(srcFunArrowMis())) else if (!rejects(srcFunArrowApply(), "arg type mismatch")) Str.concat("funArrowApply ", Check.check(srcFunArrowApply())) else if (!rejects(srcFunListApply(), "apply needs a function")) Str.concat("funListApply ", Check.check(srcFunListApply())) else if (Check.check(srcFunSubstOk()) != "[]") Str.concat("funSubstOk ", Check.check(srcFunSubstOk())) else if (!rejects(srcFunSubstMis(), "does not match declared")) Str.concat("funSubstMis ", Check.check(srcFunSubstMis())) else if (Check.check(srcFunSubstId()) != "[]") Str.concat("funSubstId ", Check.check(srcFunSubstId())) else if (Check.check(srcFunTupFieldOk()) != "[]") Str.concat("funTupFieldOk ", Check.check(srcFunTupFieldOk())) else if (!rejects(srcFunTupFieldMis(), "tuple has no field")) Str.concat("funTupFieldMis ", Check.check(srcFunTupFieldMis())) else if (!rejects(srcFunTupFieldKeep(), "tuple has no field")) Str.concat("funTupFieldKeep ", Check.check(srcFunTupFieldKeep())) else if (!funKitHeadTypes()) dumpFunKitHead() else "funArrow-other" def dumpFunKitHead(): String = if (Type.headName(Type.parse("List[Int] => String")) != "List[Int] => String") "headNameFun" else if (Type.headName(Type.parse("List[Int]")) != "List") "headNameList" else if (Check.check(srcFunListTailOk()) != "[]") Str.concat("funListTailOk ", Check.check(srcFunListTailOk())) else if (!rejects(srcFunListTailMis(), "unknown function .tail")) Str.concat("funListTailMis ", Check.check(srcFunListTailMis())) else if (Check.check(srcFunIoForeverOk()) != "[]") Str.concat("funIoForeverOk ", Check.check(srcFunIoForeverOk())) else if (!rejects(srcFunIoForeverMis(), "unknown function .forever")) Str.concat("funIoForeverMis ", Check.check(srcFunIoForeverMis())) else if (Check.check(srcViewEachOk()) != "[]") Str.concat("viewEachOk ", Check.check(srcViewEachOk())) else if (!rejects(srcFunViewEachMis(), "View.each arg type mismatch")) Str.concat("funViewEachMis ", Check.check(srcFunViewEachMis())) else if (Check.check(srcFunSubstId()) != "[]") Str.concat("funKitHeadEq ", Check.check(srcFunSubstId())) else "funKitHead-other" @@ -1194,6 +1194,22 @@ def wrong(e: E): Int = e match { case E.B => nope }""", "unbound variable nope") +def expectedLamTypes(): Bool = + Check.check("""def apply(f: Int => String): String = f(1) +def right(): String = apply(n => Str.fromInt(n)) +@main def main: IO[Unit] = IO.println(right()) +""") == "[]" && rejects("""def apply(f: Int => String): String = f(1) +def wrong(): String = apply(n => n) +""", "apply arg type mismatch: expected Int => String, got Int => Int") && Check.check("""def apply(f: Int => String): String = f(1) +def right(): String = apply((n: Int) => Str.fromInt(n)) +""") == "[]" && rejects("""def apply(f: Int => String): String = f(1) +def wrong(): String = apply((s: String) => s) +""", "lambda arg type mismatch: expected Int, got String") && Check.check("""def applyBoth(f: Int => Int, g: String => String): Int = f(1) +def right(): Int = applyBoth(n => n + 1, s => s) +""") == "[]" && rejects("""def applyBoth(f: Int => Int, g: String => String): Int = f(1) +def wrong(): Int = applyBoth(n => n + 1, s => 1) +""", "applyBoth arg type mismatch: expected String => String, got String => Int") && Check.check("def right(): Int => String = n => Str.fromInt(n)") == "[]" && rejects("def wrong(): Int => String = n => n", "def wrong body Int => Int does not match declared Int => String") + def funLookup(): Bool = funLookupDefs(Parse.parseFiles(("A", """def pick(n: Int): Int = n def skip(): Int = 0 From 7b4aa05c72d638e68a9e143317a995b2c0c15b04 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 16 Sep 2026 22:13:49 -0400 Subject: [PATCH 11/23] Intern emitted string literals behind a pinned rc. Every evaluation of a string literal allocated a fresh SzString through sz_string_from_cstr: malloc, copy, UTF-8 measure, and the release chain when the value died. Match arms comparing against a literal paid the same cost per comparison. A gprof profile of scuzz check examples/compiler put sz_string_from_cstr and string_alloc at about 8 percent of self time, plus their share of the 38 percent RC churn. Emitted literal sites now call sz_string_lit. The first call builds the string and pins its rc at SZ_RC_PINNED; later evaluations share one allocation from a hash table keyed by content. sz_retain and sz_release skip pinned blocks, and sz_alloc_rc_sum excludes them so TestRuntime leak oracles stay paired. Runtime data still flows through sz_string_from_cstr; only compile-time literals intern. The match-memory probe warms up before it measures: first-touch interning is a one-time, program-size-bounded allocation, and the steady-state delta is still zero. IR goldens in examples/cli and examples/codegen name sz_string_lit. scuzz check examples/compiler drops from 20.2 s to 16.9 s wall. A cold scuzz build examples/tyck drops from 70 s to 34 s. Runtime tests, ASan, LLVM IR fixed-point, tyck oracle, codegen oracle, kernel suite, and the UI slice pass. --- crates/runtime/include/scuzz_rt.h | 1 + crates/runtime/src/runtime.c | 50 ++++++++++++++++++++++- docs/gaps.md | 2 +- examples/cli/src/Main.scuzz | 2 +- examples/codegen/src/Main.scuzz | 68 +++++++++++++++---------------- examples/compiler/src/Emit.scuzz | 12 +++--- scripts/ci.sh | 12 ++++++ 7 files changed, 103 insertions(+), 44 deletions(-) diff --git a/crates/runtime/include/scuzz_rt.h b/crates/runtime/include/scuzz_rt.h index 50a011b2..075d28a0 100644 --- a/crates/runtime/include/scuzz_rt.h +++ b/crates/runtime/include/scuzz_rt.h @@ -101,6 +101,7 @@ typedef struct SzString { } SzString; SzString *sz_string_from_cstr(const char *cstr); +SzString *sz_string_lit(const char *cstr); SzString *sz_string_from_bytes(const char *bytes, size_t len); const char *sz_string_cstr(const SzString *s); void sz_string_free(SzString *s); diff --git a/crates/runtime/src/runtime.c b/crates/runtime/src/runtime.c index 56b43771..c49c66da 100644 --- a/crates/runtime/src/runtime.c +++ b/crates/runtime/src/runtime.c @@ -56,6 +56,8 @@ static SzIo *attempt_drop(SzIo *inner) { #define SZ_ALLOC_MAGIC 0x535A414Cu /* 'SZAL' */ #define SZ_RC_MAGIC 0x535A5243u /* 'SZRC' */ #define SZ_RC_TOMB 0x535A544Du /* 'SZTM' */ +/* Pinned rc: an interned literal that retain and release never touch. */ +#define SZ_RC_PINNED UINT32_MAX typedef struct SzRcHdr { uint32_t magic; @@ -476,7 +478,7 @@ uint64_t sz_alloc_rc_sum(void) { uint64_t n = 0; SzRcHdr *h; for (h = g_live; h; h = h->next) { - if (h->magic == SZ_RC_MAGIC) + if (h->magic == SZ_RC_MAGIC && h->rc != SZ_RC_PINNED) n += h->rc; } return n; @@ -545,9 +547,13 @@ void *sz_rc_alloc(size_t size, uint32_t kind) { } void sz_retain(void *ptr) { + SzRcHdr *h; if (!sz_is_rc(ptr)) return; - sz_rc_hdr(ptr)->rc += 1; + h = sz_rc_hdr(ptr); + if (h->rc == SZ_RC_PINNED) + return; + h->rc += 1; } uint32_t sz_rc_kind(const void *ptr) { @@ -570,6 +576,8 @@ void sz_release(void *ptr) { return; } h = sz_rc_hdr(ptr); + if (h->rc == SZ_RC_PINNED) + return; if (h->rc > 1) { h->rc -= 1; return; @@ -905,6 +913,44 @@ SzString *sz_string_from_cstr(const char *cstr) { return sz_string_from_bytes(cstr, strlen(cstr)); } +/* Interned string literals. Emitted code calls this for compile-time + * literals only; runtime data goes through sz_string_from_cstr. The first + * call builds the string and pins its rc, so later evaluations share one + * allocation that retain and release never touch. */ +#define SZ_LIT_BUCKETS 4096 +typedef struct SzLitEnt { + struct SzLitEnt *next; + SzString *s; +} SzLitEnt; +static SzLitEnt *g_lits[SZ_LIT_BUCKETS]; + +SzString *sz_string_lit(const char *cstr) { + size_t len; + size_t i; + uint32_t hash; + SzLitEnt *e; + SzString *s; + if (!cstr) + sz_panic("sz_string_lit(null)"); + len = strlen(cstr); + hash = 5381; + for (i = 0; i < len; i++) + hash = hash * 33 + (unsigned char)cstr[i]; + hash %= SZ_LIT_BUCKETS; + for (e = g_lits[hash]; e; e = e->next) + if (e->s->len == len && !memcmp(e->s->data, cstr, len)) + return e->s; + s = sz_string_from_bytes(cstr, len); + sz_rc_hdr(s)->rc = SZ_RC_PINNED; + e = (SzLitEnt *)malloc(sizeof(*e)); + if (!e) + sz_panic("out of memory"); + e->s = s; + e->next = g_lits[hash]; + g_lits[hash] = e; + return s; +} + const char *sz_string_cstr(const SzString *s) { return s && s->data ? s->data : ""; } diff --git a/docs/gaps.md b/docs/gaps.md index df7e850c..1bc0a74a 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -39,7 +39,7 @@ Resolve these gaps when they prevent ordinary language use. 1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions and payloads seen through a lambda parameter over a collection. Applying an env-bound lambda with an unresolved parameter letter to a concrete argument fails check. An unannotated lambda in argument or def-body position binds its parameter from the expected function type, so a loose lambda can no longer escape through an expected function type. Param letters (`A`/`E`) still unify elsewhere in `Type.eq`. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest. A nested constructor, tuple, as, or cons pattern in a constructor field or tuple component fails check. A bare enum case, float literal, or list literal in those positions also fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. -2. **Compile-time performance** — `scuzz check examples/compiler` is 16 s. A cold `scuzz build examples/tyck` is 1 m 10 s. Remaining cost: RC retain/release churn and `sz_list_concat` in string building. Coverage still parses a compiled graph that differs from live. +2. **Compile-time performance** — `scuzz check examples/compiler` is 17 s. A cold `scuzz build examples/tyck` is 34 s. Emitted string literals intern to pinned allocations. Remaining cost: RC retain/release churn and `sz_list_concat` in string building. Coverage still parses a compiled graph that differs from live. ### Table-stakes diff --git a/examples/cli/src/Main.scuzz b/examples/cli/src/Main.scuzz index 4dfbe9e9..a17e3bd1 100644 --- a/examples/cli/src/Main.scuzz +++ b/examples/cli/src/Main.scuzz @@ -103,7 +103,7 @@ def srcIrHi(): String = "@main def main: IO[Unit] =\n IO.println(\"Hi\")\n" def wantIrHi(): String = - "@.str0 = private unnamed_addr constant [3 x i8] c\"Hi\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_gep = getelementptr inbounds [3 x i8], ptr @.str0, i64 0, i64 0\n %build_ss = call ptr @sz_string_from_cstr(ptr %build_gep)\n %build_io = call ptr @sz_io_println(ptr %build_ss)\n call void @sz_release(ptr %build_ss)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [3 x i8] c\"Hi\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_gep = getelementptr inbounds [3 x i8], ptr @.str0, i64 0, i64 0\n %build_ss = call ptr @sz_string_lit(ptr %build_gep)\n %build_io = call ptr @sz_io_println(ptr %build_ss)\n call void @sz_release(ptr %build_ss)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcTyUnbound(): String = """@main def main: IO[Unit] = diff --git a/examples/codegen/src/Main.scuzz b/examples/codegen/src/Main.scuzz index c427fda9..e4bf8a95 100644 --- a/examples/codegen/src/Main.scuzz +++ b/examples/codegen/src/Main.scuzz @@ -6,7 +6,7 @@ def srcHi(): String = "@main def main: IO[Unit] =\n IO.println(\"Hi\")\n" def wantHi(): String = - "@.str0 = private unnamed_addr constant [3 x i8] c\"Hi\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_gep = getelementptr inbounds [3 x i8], ptr @.str0, i64 0, i64 0\n %build_ss = call ptr @sz_string_from_cstr(ptr %build_gep)\n %build_io = call ptr @sz_io_println(ptr %build_ss)\n call void @sz_release(ptr %build_ss)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [3 x i8] c\"Hi\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_gep = getelementptr inbounds [3 x i8], ptr @.str0, i64 0, i64 0\n %build_ss = call ptr @sz_string_lit(ptr %build_gep)\n %build_io = call ptr @sz_io_println(ptr %build_ss)\n call void @sz_release(ptr %build_ss)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcSum(): String = """@main def main: IO[Unit] = @@ -64,13 +64,13 @@ def srcTcoNest(): String = "def skipSpace(s: String, i: Int): Int =\n if (i >= Str.len(s)) i else if (Str.charAt(s, i) == 32) skipSpace(s, i + 1) else i\n\n@main def main: IO[Unit] =\n IO.println(Str.fromInt(skipSpace(\" x\", 0)))\n" def wantTcoNest(): String = - "@.str0 = private unnamed_addr constant [4 x i8] c\" x\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:5:3\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str3 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@63#t\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@70#e\\00\", align 1\n@.str5 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@98#t\\00\", align 1\n@.str6 = private unnamed_addr constant [21 x i8] c\"Main.scuzz:1:5@123#e\\00\", align 1\n\ndefine internal i64 @sz_user_Main_skipSpace(ptr %s_in, i64 %i_in) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %s_slot = alloca ptr\n store ptr %s_in, ptr %s_slot\n %s_held = alloca i1\n store i1 false, ptr %s_held\n %i_slot = alloca i64\n store i64 %i_in, ptr %i_slot\n br label %tco_loop\ntco_loop:\n %s = load ptr, ptr %s_slot\n %i = load i64, ptr %i_slot\n %body_ic_r_v = call i64 @sz_string_ulen(ptr %s)\n %body_ic_cmp = icmp sge i64 %i, %body_ic_r_v\n %body_ic_v = zext i1 %body_ic_cmp to i64\n %body_cmp = icmp ne i64 %body_ic_v, 0\n br i1 %body_cmp, label %body_then_0, label %body_else_0\nbody_then_0:\n %body_cvtg0 = getelementptr inbounds [20 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cvtg0)\n %tco_hv0_body_t_rb = load i1, ptr %s_held\n br i1 %tco_hv0_body_t_rb, label %tco_rel_0_body_t_rb, label %tco_skip_0_body_t_rb\ntco_rel_0_body_t_rb:\n %tco_old0_body_t_rb = load ptr, ptr %s_slot\n call void @sz_release(ptr %tco_old0_body_t_rb)\n br label %tco_skip_0_body_t_rb\ntco_skip_0_body_t_rb:\n call void @sz_panic_pop_src()\n ret i64 %i\nbody_else_0:\n %body_cveg0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cveg0)\n %body_e_ic_l_v = call i64 @sz_string_uchar_at(ptr %s, i64 %i)\n %body_e_ic_cmp = icmp eq i64 %body_e_ic_l_v, 32\n %body_e_ic_v = zext i1 %body_e_ic_cmp to i64\n %body_e_cmp = icmp ne i64 %body_e_ic_v, 0\n br i1 %body_e_cmp, label %body_e_then_0, label %body_e_else_0\nbody_e_then_0:\n %body_e_cvtg0 = getelementptr inbounds [20 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_e_cvtg0)\n %body_e_t_targ1_v = add i64 %i, 1\n call void @sz_retain(ptr %s)\n %tco_hv0_body_e_t = load i1, ptr %s_held\n br i1 %tco_hv0_body_e_t, label %tco_rel_0_body_e_t, label %tco_skip_0_body_e_t\ntco_rel_0_body_e_t:\n %tco_old0_body_e_t = load ptr, ptr %s_slot\n call void @sz_release(ptr %tco_old0_body_e_t)\n br label %tco_skip_0_body_e_t\ntco_skip_0_body_e_t:\n store ptr %s, ptr %s_slot\n store i64 %body_e_t_targ1_v, ptr %i_slot\n store i1 true, ptr %s_held\n br label %tco_loop\nbody_e_else_0:\n %body_e_cveg0 = getelementptr inbounds [21 x i8], ptr @.str6, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_e_cveg0)\n %tco_hv0_body_e_e_rb = load i1, ptr %s_held\n br i1 %tco_hv0_body_e_e_rb, label %tco_rel_0_body_e_e_rb, label %tco_skip_0_body_e_e_rb\ntco_rel_0_body_e_e_rb:\n %tco_old0_body_e_e_rb = load ptr, ptr %s_slot\n call void @sz_release(ptr %tco_old0_body_e_e_rb)\n br label %tco_skip_0_body_e_e_rb\ntco_skip_0_body_e_e_rb:\n call void @sz_panic_pop_src()\n ret i64 %i\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_gep = getelementptr inbounds [4 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_arg0_gep)\n %build_e_arg0_v = call i64 @sz_user_Main_skipSpace(ptr %build_e_arg0_arg0_s, i64 0)\n call void @sz_release(ptr %build_e_arg0_arg0_s)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [4 x i8] c\" x\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:5:3\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str3 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@63#t\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@70#e\\00\", align 1\n@.str5 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@98#t\\00\", align 1\n@.str6 = private unnamed_addr constant [21 x i8] c\"Main.scuzz:1:5@123#e\\00\", align 1\n\ndefine internal i64 @sz_user_Main_skipSpace(ptr %s_in, i64 %i_in) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %s_slot = alloca ptr\n store ptr %s_in, ptr %s_slot\n %s_held = alloca i1\n store i1 false, ptr %s_held\n %i_slot = alloca i64\n store i64 %i_in, ptr %i_slot\n br label %tco_loop\ntco_loop:\n %s = load ptr, ptr %s_slot\n %i = load i64, ptr %i_slot\n %body_ic_r_v = call i64 @sz_string_ulen(ptr %s)\n %body_ic_cmp = icmp sge i64 %i, %body_ic_r_v\n %body_ic_v = zext i1 %body_ic_cmp to i64\n %body_cmp = icmp ne i64 %body_ic_v, 0\n br i1 %body_cmp, label %body_then_0, label %body_else_0\nbody_then_0:\n %body_cvtg0 = getelementptr inbounds [20 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cvtg0)\n %tco_hv0_body_t_rb = load i1, ptr %s_held\n br i1 %tco_hv0_body_t_rb, label %tco_rel_0_body_t_rb, label %tco_skip_0_body_t_rb\ntco_rel_0_body_t_rb:\n %tco_old0_body_t_rb = load ptr, ptr %s_slot\n call void @sz_release(ptr %tco_old0_body_t_rb)\n br label %tco_skip_0_body_t_rb\ntco_skip_0_body_t_rb:\n call void @sz_panic_pop_src()\n ret i64 %i\nbody_else_0:\n %body_cveg0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cveg0)\n %body_e_ic_l_v = call i64 @sz_string_uchar_at(ptr %s, i64 %i)\n %body_e_ic_cmp = icmp eq i64 %body_e_ic_l_v, 32\n %body_e_ic_v = zext i1 %body_e_ic_cmp to i64\n %body_e_cmp = icmp ne i64 %body_e_ic_v, 0\n br i1 %body_e_cmp, label %body_e_then_0, label %body_e_else_0\nbody_e_then_0:\n %body_e_cvtg0 = getelementptr inbounds [20 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_e_cvtg0)\n %body_e_t_targ1_v = add i64 %i, 1\n call void @sz_retain(ptr %s)\n %tco_hv0_body_e_t = load i1, ptr %s_held\n br i1 %tco_hv0_body_e_t, label %tco_rel_0_body_e_t, label %tco_skip_0_body_e_t\ntco_rel_0_body_e_t:\n %tco_old0_body_e_t = load ptr, ptr %s_slot\n call void @sz_release(ptr %tco_old0_body_e_t)\n br label %tco_skip_0_body_e_t\ntco_skip_0_body_e_t:\n store ptr %s, ptr %s_slot\n store i64 %body_e_t_targ1_v, ptr %i_slot\n store i1 true, ptr %s_held\n br label %tco_loop\nbody_e_else_0:\n %body_e_cveg0 = getelementptr inbounds [21 x i8], ptr @.str6, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_e_cveg0)\n %tco_hv0_body_e_e_rb = load i1, ptr %s_held\n br i1 %tco_hv0_body_e_e_rb, label %tco_rel_0_body_e_e_rb, label %tco_skip_0_body_e_e_rb\ntco_rel_0_body_e_e_rb:\n %tco_old0_body_e_e_rb = load ptr, ptr %s_slot\n call void @sz_release(ptr %tco_old0_body_e_e_rb)\n br label %tco_skip_0_body_e_e_rb\ntco_skip_0_body_e_e_rb:\n call void @sz_panic_pop_src()\n ret i64 %i\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_gep = getelementptr inbounds [4 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_s = call ptr @sz_string_lit(ptr %build_e_arg0_arg0_gep)\n %build_e_arg0_v = call i64 @sz_user_Main_skipSpace(ptr %build_e_arg0_arg0_s, i64 0)\n call void @sz_release(ptr %build_e_arg0_arg0_s)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcTcoOwn(): String = "def fill(n: Int, b: Builder): String =\n if (n <= 0) Builder.result(b) else fill(n - 1, Builder.append(b, \"x\"))\n\n@main def main: IO[Unit] =\n IO.println(fill(3, Builder.empty()))\n" def wantTcoOwn(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:5:3\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str3 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@53#t\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@76#e\\00\", align 1\n\ndefine internal ptr @sz_user_Main_fill(i64 %n_in, ptr %b_in) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %n_slot = alloca i64\n store i64 %n_in, ptr %n_slot\n %b_slot = alloca ptr\n store ptr %b_in, ptr %b_slot\n %b_held = alloca i1\n store i1 false, ptr %b_held\n br label %tco_loop\ntco_loop:\n %n = load i64, ptr %n_slot\n %b = load ptr, ptr %b_slot\n %body_ic_cmp = icmp sle i64 %n, 0\n %body_ic_v = zext i1 %body_ic_cmp to i64\n %body_cmp = icmp ne i64 %body_ic_v, 0\n br i1 %body_cmp, label %body_then_0, label %body_else_0\nbody_then_0:\n %body_cvtg0 = getelementptr inbounds [20 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cvtg0)\n %body_t_b_v = call ptr @sz_builder_result(ptr %b)\n %tco_hv1_body_t_rb = load i1, ptr %b_held\n br i1 %tco_hv1_body_t_rb, label %tco_rel_1_body_t_rb, label %tco_skip_1_body_t_rb\ntco_rel_1_body_t_rb:\n %tco_old1_body_t_rb = load ptr, ptr %b_slot\n call void @sz_release(ptr %tco_old1_body_t_rb)\n br label %tco_skip_1_body_t_rb\ntco_skip_1_body_t_rb:\n call void @sz_panic_pop_src()\n ret ptr %body_t_b_v\nbody_else_0:\n %body_cveg0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cveg0)\n %body_e_targ0_v = sub i64 %n, 1\n %body_e_targ1_arg1_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %body_e_targ1_arg1_s = call ptr @sz_string_from_cstr(ptr %body_e_targ1_arg1_gep)\n %body_e_targ1_v = call ptr @sz_builder_append(ptr %b, ptr %body_e_targ1_arg1_s)\n call void @sz_release(ptr %body_e_targ1_arg1_s)\n call void @sz_retain(ptr %body_e_targ1_v)\n %tco_hv1_body_e = load i1, ptr %b_held\n br i1 %tco_hv1_body_e, label %tco_rel_1_body_e, label %tco_skip_1_body_e\ntco_rel_1_body_e:\n %tco_old1_body_e = load ptr, ptr %b_slot\n call void @sz_release(ptr %tco_old1_body_e)\n br label %tco_skip_1_body_e\ntco_skip_1_body_e:\n store i64 %body_e_targ0_v, ptr %n_slot\n store ptr %body_e_targ1_v, ptr %b_slot\n store i1 true, ptr %b_held\n call void @sz_release(ptr %body_e_targ1_v)\n br label %tco_loop\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg1_v = call ptr @sz_builder_new()\n %build_e_v = call ptr @sz_user_Main_fill(i64 3, ptr %build_e_arg1_v)\n call void @sz_release(ptr %build_e_arg1_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:5:3\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str3 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@53#t\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@76#e\\00\", align 1\n\ndefine internal ptr @sz_user_Main_fill(i64 %n_in, ptr %b_in) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %n_slot = alloca i64\n store i64 %n_in, ptr %n_slot\n %b_slot = alloca ptr\n store ptr %b_in, ptr %b_slot\n %b_held = alloca i1\n store i1 false, ptr %b_held\n br label %tco_loop\ntco_loop:\n %n = load i64, ptr %n_slot\n %b = load ptr, ptr %b_slot\n %body_ic_cmp = icmp sle i64 %n, 0\n %body_ic_v = zext i1 %body_ic_cmp to i64\n %body_cmp = icmp ne i64 %body_ic_v, 0\n br i1 %body_cmp, label %body_then_0, label %body_else_0\nbody_then_0:\n %body_cvtg0 = getelementptr inbounds [20 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cvtg0)\n %body_t_b_v = call ptr @sz_builder_result(ptr %b)\n %tco_hv1_body_t_rb = load i1, ptr %b_held\n br i1 %tco_hv1_body_t_rb, label %tco_rel_1_body_t_rb, label %tco_skip_1_body_t_rb\ntco_rel_1_body_t_rb:\n %tco_old1_body_t_rb = load ptr, ptr %b_slot\n call void @sz_release(ptr %tco_old1_body_t_rb)\n br label %tco_skip_1_body_t_rb\ntco_skip_1_body_t_rb:\n call void @sz_panic_pop_src()\n ret ptr %body_t_b_v\nbody_else_0:\n %body_cveg0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cveg0)\n %body_e_targ0_v = sub i64 %n, 1\n %body_e_targ1_arg1_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %body_e_targ1_arg1_s = call ptr @sz_string_lit(ptr %body_e_targ1_arg1_gep)\n %body_e_targ1_v = call ptr @sz_builder_append(ptr %b, ptr %body_e_targ1_arg1_s)\n call void @sz_release(ptr %body_e_targ1_arg1_s)\n call void @sz_retain(ptr %body_e_targ1_v)\n %tco_hv1_body_e = load i1, ptr %b_held\n br i1 %tco_hv1_body_e, label %tco_rel_1_body_e, label %tco_skip_1_body_e\ntco_rel_1_body_e:\n %tco_old1_body_e = load ptr, ptr %b_slot\n call void @sz_release(ptr %tco_old1_body_e)\n br label %tco_skip_1_body_e\ntco_skip_1_body_e:\n store i64 %body_e_targ0_v, ptr %n_slot\n store ptr %body_e_targ1_v, ptr %b_slot\n store i1 true, ptr %b_held\n call void @sz_release(ptr %body_e_targ1_v)\n br label %tco_loop\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg1_v = call ptr @sz_builder_new()\n %build_e_v = call ptr @sz_user_Main_fill(i64 3, ptr %build_e_arg1_v)\n call void @sz_release(ptr %build_e_arg1_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def internInterpLit(): Bool = List.join(Emit.internReady(Emit.internInterp(Emit.noStr(), "hi${1}lo", 0, Builder.empty())), ",") == "hi,lo" @@ -102,31 +102,31 @@ def srcHello(): String = "@main def main: IO[Unit] =\n IO.println(\"Hello, Scuzz!\").flatMap(_ => IO.println(\"ready.\"))\n" def wantHello(): String = - "@.str0 = private unnamed_addr constant [14 x i8] c\"Hello, Scuzz!\\00\", align 1\n@.str1 = private unnamed_addr constant [7 x i8] c\"ready.\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine internal ptr @sz_cont_0(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %c0_gep = getelementptr inbounds [7 x i8], ptr @.str1, i64 0, i64 0\n %c0_ss = call ptr @sz_string_from_cstr(ptr %c0_gep)\n %c0_io = call ptr @sz_io_println(ptr %c0_ss)\n call void @sz_release(ptr %c0_ss)\n call void @sz_panic_pop_src()\n ret ptr %c0_io\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_in_gep = getelementptr inbounds [14 x i8], ptr @.str0, i64 0, i64 0\n %build_in_ss = call ptr @sz_string_from_cstr(ptr %build_in_gep)\n %build_in_io = call ptr @sz_io_println(ptr %build_in_ss)\n call void @sz_release(ptr %build_in_ss)\n %build_fm = call ptr @sz_io_flatmap(ptr %build_in_io, ptr @sz_cont_0, ptr null)\n call void @sz_release(ptr %build_in_io)\n call void @sz_release(ptr null)\n %rc = call i32 @sz_runtime_main_args(ptr %build_fm, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [14 x i8] c\"Hello, Scuzz!\\00\", align 1\n@.str1 = private unnamed_addr constant [7 x i8] c\"ready.\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine internal ptr @sz_cont_0(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %c0_gep = getelementptr inbounds [7 x i8], ptr @.str1, i64 0, i64 0\n %c0_ss = call ptr @sz_string_lit(ptr %c0_gep)\n %c0_io = call ptr @sz_io_println(ptr %c0_ss)\n call void @sz_release(ptr %c0_ss)\n call void @sz_panic_pop_src()\n ret ptr %c0_io\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_in_gep = getelementptr inbounds [14 x i8], ptr @.str0, i64 0, i64 0\n %build_in_ss = call ptr @sz_string_lit(ptr %build_in_gep)\n %build_in_io = call ptr @sz_io_println(ptr %build_in_ss)\n call void @sz_release(ptr %build_in_ss)\n %build_fm = call ptr @sz_io_flatmap(ptr %build_in_io, ptr @sz_cont_0, ptr null)\n call void @sz_release(ptr %build_in_io)\n call void @sz_release(ptr null)\n %rc = call i32 @sz_runtime_main_args(ptr %build_fm, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcIf(): String = "@main def main: IO[Unit] =\n IO.println(if (true) \"fmt-ok\" else \"fmt-bad\")\n" def wantIf(): String = - "@.str0 = private unnamed_addr constant [7 x i8] c\"fmt-ok\\00\", align 1\n@.str1 = private unnamed_addr constant [8 x i8] c\"fmt-bad\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n@.str3 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:2:3@50#t\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:2:3@64#e\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_cmp = icmp ne i64 1, 0\n br i1 %build_e_cmp, label %build_e_then_0, label %build_e_else_0\nbuild_e_then_0:\n %build_e_cvtg0 = getelementptr inbounds [20 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_e_cvtg0)\n %build_e_t0_gep = getelementptr inbounds [7 x i8], ptr @.str0, i64 0, i64 0\n %build_e_t0_s = call ptr @sz_string_from_cstr(ptr %build_e_t0_gep)\n br label %build_e_tj_0\nbuild_e_tj_0:\n br label %build_e_merge_0\nbuild_e_else_0:\n %build_e_cveg0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_e_cveg0)\n %build_e_e0_gep = getelementptr inbounds [8 x i8], ptr @.str1, i64 0, i64 0\n %build_e_e0_s = call ptr @sz_string_from_cstr(ptr %build_e_e0_gep)\n br label %build_e_ej_0\nbuild_e_ej_0:\n br label %build_e_merge_0\nbuild_e_merge_0:\n %build_e_phi = phi ptr [ %build_e_t0_s, %build_e_tj_0 ], [ %build_e_e0_s, %build_e_ej_0 ]\n %build_io = call ptr @sz_io_println(ptr %build_e_phi)\n call void @sz_release(ptr %build_e_phi)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [7 x i8] c\"fmt-ok\\00\", align 1\n@.str1 = private unnamed_addr constant [8 x i8] c\"fmt-bad\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n@.str3 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:2:3@50#t\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:2:3@64#e\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_cmp = icmp ne i64 1, 0\n br i1 %build_e_cmp, label %build_e_then_0, label %build_e_else_0\nbuild_e_then_0:\n %build_e_cvtg0 = getelementptr inbounds [20 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_e_cvtg0)\n %build_e_t0_gep = getelementptr inbounds [7 x i8], ptr @.str0, i64 0, i64 0\n %build_e_t0_s = call ptr @sz_string_lit(ptr %build_e_t0_gep)\n br label %build_e_tj_0\nbuild_e_tj_0:\n br label %build_e_merge_0\nbuild_e_else_0:\n %build_e_cveg0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_e_cveg0)\n %build_e_e0_gep = getelementptr inbounds [8 x i8], ptr @.str1, i64 0, i64 0\n %build_e_e0_s = call ptr @sz_string_lit(ptr %build_e_e0_gep)\n br label %build_e_ej_0\nbuild_e_ej_0:\n br label %build_e_merge_0\nbuild_e_merge_0:\n %build_e_phi = phi ptr [ %build_e_t0_s, %build_e_tj_0 ], [ %build_e_e0_s, %build_e_ej_0 ]\n %build_io = call ptr @sz_io_println(ptr %build_e_phi)\n call void @sz_release(ptr %build_e_phi)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcTag(): String = "def tag(): String =\n \"x\"\n\n@main def main: IO[Unit] =\n IO.println(tag())\n" def wantTag(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:5:3\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n\ndefine internal ptr @sz_user_Main_tag() {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %body_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %body_s = call ptr @sz_string_from_cstr(ptr %body_gep)\n call void @sz_panic_pop_src()\n ret ptr %body_s\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_v = call ptr @sz_user_Main_tag()\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:5:3\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n\ndefine internal ptr @sz_user_Main_tag() {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %body_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %body_s = call ptr @sz_string_lit(ptr %body_gep)\n call void @sz_panic_pop_src()\n ret ptr %body_s\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_v = call ptr @sz_user_Main_tag()\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcCat(): String = "@main def main: IO[Unit] =\n IO.println(Str.concat(\"a\", \"b\"))\n" def wantCat(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_gep)\n %build_e_arg1_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg1_s = call ptr @sz_string_from_cstr(ptr %build_e_arg1_gep)\n %build_e_v = call ptr @sz_string_concat(ptr %build_e_arg0_s, ptr %build_e_arg1_s)\n call void @sz_release(ptr %build_e_arg0_s)\n call void @sz_release(ptr %build_e_arg1_s)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_s = call ptr @sz_string_lit(ptr %build_e_arg0_gep)\n %build_e_arg1_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg1_s = call ptr @sz_string_lit(ptr %build_e_arg1_gep)\n %build_e_v = call ptr @sz_string_concat(ptr %build_e_arg0_s, ptr %build_e_arg1_s)\n call void @sz_release(ptr %build_e_arg0_s)\n call void @sz_release(ptr %build_e_arg1_s)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcFmt(): String = "def allOk(): Bool =\n true\n\n@main def main: IO[Unit] =\n IO.println(if (allOk()) \"fmt-ok\" else \"fmt-bad\")\n" def wantFmt(): String = - "@.str0 = private unnamed_addr constant [7 x i8] c\"fmt-ok\\00\", align 1\n@.str1 = private unnamed_addr constant [8 x i8] c\"fmt-bad\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:5:3\\00\", align 1\n@.str3 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:5:3@81#t\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:5:3@95#e\\00\", align 1\n@.str5 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n\ndefine internal i64 @sz_user_Main_allOk() {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n call void @sz_panic_pop_src()\n ret i64 1\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_ic_v = call i64 @sz_user_Main_allOk()\n %build_e_cmp = icmp ne i64 %build_e_ic_v, 0\n br i1 %build_e_cmp, label %build_e_then_0, label %build_e_else_0\nbuild_e_then_0:\n %build_e_cvtg0 = getelementptr inbounds [20 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_e_cvtg0)\n %build_e_t0_gep = getelementptr inbounds [7 x i8], ptr @.str0, i64 0, i64 0\n %build_e_t0_s = call ptr @sz_string_from_cstr(ptr %build_e_t0_gep)\n br label %build_e_tj_0\nbuild_e_tj_0:\n br label %build_e_merge_0\nbuild_e_else_0:\n %build_e_cveg0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_e_cveg0)\n %build_e_e0_gep = getelementptr inbounds [8 x i8], ptr @.str1, i64 0, i64 0\n %build_e_e0_s = call ptr @sz_string_from_cstr(ptr %build_e_e0_gep)\n br label %build_e_ej_0\nbuild_e_ej_0:\n br label %build_e_merge_0\nbuild_e_merge_0:\n %build_e_phi = phi ptr [ %build_e_t0_s, %build_e_tj_0 ], [ %build_e_e0_s, %build_e_ej_0 ]\n %build_io = call ptr @sz_io_println(ptr %build_e_phi)\n call void @sz_release(ptr %build_e_phi)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [7 x i8] c\"fmt-ok\\00\", align 1\n@.str1 = private unnamed_addr constant [8 x i8] c\"fmt-bad\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:5:3\\00\", align 1\n@.str3 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:5:3@81#t\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:5:3@95#e\\00\", align 1\n@.str5 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n\ndefine internal i64 @sz_user_Main_allOk() {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n call void @sz_panic_pop_src()\n ret i64 1\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_ic_v = call i64 @sz_user_Main_allOk()\n %build_e_cmp = icmp ne i64 %build_e_ic_v, 0\n br i1 %build_e_cmp, label %build_e_then_0, label %build_e_else_0\nbuild_e_then_0:\n %build_e_cvtg0 = getelementptr inbounds [20 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_e_cvtg0)\n %build_e_t0_gep = getelementptr inbounds [7 x i8], ptr @.str0, i64 0, i64 0\n %build_e_t0_s = call ptr @sz_string_lit(ptr %build_e_t0_gep)\n br label %build_e_tj_0\nbuild_e_tj_0:\n br label %build_e_merge_0\nbuild_e_else_0:\n %build_e_cveg0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_e_cveg0)\n %build_e_e0_gep = getelementptr inbounds [8 x i8], ptr @.str1, i64 0, i64 0\n %build_e_e0_s = call ptr @sz_string_lit(ptr %build_e_e0_gep)\n br label %build_e_ej_0\nbuild_e_ej_0:\n br label %build_e_merge_0\nbuild_e_merge_0:\n %build_e_phi = phi ptr [ %build_e_t0_s, %build_e_tj_0 ], [ %build_e_e0_s, %build_e_ej_0 ]\n %build_io = call ptr @sz_io_println(ptr %build_e_phi)\n call void @sz_release(ptr %build_e_phi)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcCmp(): String = """@main def main: IO[Unit] = @@ -151,7 +151,7 @@ def srcColor(): String = "enum Color:\n case Red\n case Blue\ndef show(c: Color): String =\n c match {\n case Color.Red => \"r\"\n case Color.Blue => \"b\"\n }\n@main def main: IO[Unit] =\n IO.println(show(Color.Red))\n" def wantColor(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"r\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [16 x i8] c\"Main.scuzz:10:3\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:4:5\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:4:5@98#0\\00\", align 1\n@.str5 = private unnamed_addr constant [21 x i8] c\"Main.scuzz:4:5@125#1\\00\", align 1\n\ndefine internal ptr @sz_user_Main_show(ptr %c) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tg1 = call i32 @sz_adt_tag(ptr %c)\n %body_p0_0_eq1 = icmp eq i32 %body_p0_0_tg1, 0\n br i1 %body_p0_0_eq1, label %body_p0_0_m1, label %body_try_0_1\nbody_p0_0_m1:\n br label %body_ok_0_0\nbody_try_0_1:\n %body_p0_1_tg2 = call i32 @sz_adt_tag(ptr %c)\n %body_p0_1_eq2 = icmp eq i32 %body_p0_1_tg2, 1\n br i1 %body_p0_1_eq2, label %body_p0_1_m2, label %body_default_0\nbody_p0_1_m2:\n br label %body_ok_0_1\nbody_default_0:\n br label %body_merge_0\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n %body_a0_0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %body_a0_0_s = call ptr @sz_string_from_cstr(ptr %body_a0_0_gep)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_ok_0_1:\n %body_cv1g0 = getelementptr inbounds [21 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv1g0)\n %body_a0_1_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %body_a0_1_s = call ptr @sz_string_from_cstr(ptr %body_a0_1_gep)\n br label %body_aj_0_1\nbody_aj_0_1:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi ptr [ %body_a0_0_s, %body_aj_0_0 ], [ %body_a0_1_s, %body_aj_0_1 ], [ null, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret ptr %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [16 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_adt = call ptr @sz_adt_new(i32 0, ptr null)\n %build_e_v = call ptr @sz_user_Main_show(ptr %build_e_arg0_adt)\n call void @sz_release(ptr %build_e_arg0_adt)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"r\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [16 x i8] c\"Main.scuzz:10:3\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:4:5\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:4:5@98#0\\00\", align 1\n@.str5 = private unnamed_addr constant [21 x i8] c\"Main.scuzz:4:5@125#1\\00\", align 1\n\ndefine internal ptr @sz_user_Main_show(ptr %c) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tg1 = call i32 @sz_adt_tag(ptr %c)\n %body_p0_0_eq1 = icmp eq i32 %body_p0_0_tg1, 0\n br i1 %body_p0_0_eq1, label %body_p0_0_m1, label %body_try_0_1\nbody_p0_0_m1:\n br label %body_ok_0_0\nbody_try_0_1:\n %body_p0_1_tg2 = call i32 @sz_adt_tag(ptr %c)\n %body_p0_1_eq2 = icmp eq i32 %body_p0_1_tg2, 1\n br i1 %body_p0_1_eq2, label %body_p0_1_m2, label %body_default_0\nbody_p0_1_m2:\n br label %body_ok_0_1\nbody_default_0:\n br label %body_merge_0\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n %body_a0_0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %body_a0_0_s = call ptr @sz_string_lit(ptr %body_a0_0_gep)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_ok_0_1:\n %body_cv1g0 = getelementptr inbounds [21 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv1g0)\n %body_a0_1_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %body_a0_1_s = call ptr @sz_string_lit(ptr %body_a0_1_gep)\n br label %body_aj_0_1\nbody_aj_0_1:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi ptr [ %body_a0_0_s, %body_aj_0_0 ], [ %body_a0_1_s, %body_aj_0_1 ], [ null, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret ptr %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [16 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_adt = call ptr @sz_adt_new(i32 0, ptr null)\n %build_e_v = call ptr @sz_user_Main_show(ptr %build_e_arg0_adt)\n call void @sz_release(ptr %build_e_arg0_adt)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcList(): String = """@main def main: IO[Unit] = @@ -216,7 +216,7 @@ def srcHeadPair(): String = """ def wantHead(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"0\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n@.str2 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:2:3@79#0\\00\", align 1\n@.str3 = private unnamed_addr constant [21 x i8] c\"Main.scuzz:2:3@130#1\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_sc_arg0_0 = call ptr @sz_list_nil()\n %build_sc_arg0_b0 = call ptr @sz_box_i64(i64 7)\n %build_sc_arg0_1 = call ptr @sz_list_cons(ptr %build_sc_arg0_b0, ptr %build_sc_arg0_0)\n call void @sz_release(ptr %build_sc_arg0_0)\n call void @sz_release(ptr %build_sc_arg0_b0)\n %build_sc_v = call ptr @sz_list_head_opt(ptr %build_sc_arg0_1)\n call void @sz_release(ptr %build_sc_arg0_1)\n br label %build_try_0_0\nbuild_try_0_0:\n %build_p0_0_tg1 = call i32 @sz_adt_tag(ptr %build_sc_v)\n %build_p0_0_eq1 = icmp eq i32 %build_p0_0_tg1, 1\n br i1 %build_p0_0_eq1, label %build_p0_0_m1, label %build_try_0_1\nbuild_p0_0_m1:\n %build_p0_0_f1_pl = call ptr @sz_adt_payload(ptr %build_sc_v)\n %build_p0_0_f1_b0 = call i64 @sz_unbox_i64(ptr %build_p0_0_f1_pl)\n br label %build_ok_0_0\nbuild_try_0_1:\n %build_p0_1_tg2 = call i32 @sz_adt_tag(ptr %build_sc_v)\n %build_p0_1_eq2 = icmp eq i32 %build_p0_1_tg2, 0\n br i1 %build_p0_1_eq2, label %build_p0_1_m2, label %build_default_0\nbuild_p0_1_m2:\n br label %build_ok_0_1\nbuild_default_0:\n br label %build_merge_0\nbuild_ok_0_0:\n %build_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_cv0g0)\n %build_a0_0_e_v = call ptr @sz_string_from_int(i64 %build_p0_0_f1_b0)\n %build_a0_0_io = call ptr @sz_io_println(ptr %build_a0_0_e_v)\n call void @sz_release(ptr %build_a0_0_e_v)\n br label %build_aj_0_0\nbuild_aj_0_0:\n br label %build_merge_0\nbuild_ok_0_1:\n %build_cv1g0 = getelementptr inbounds [21 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_cv1g0)\n %build_a0_1_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_a0_1_ss = call ptr @sz_string_from_cstr(ptr %build_a0_1_gep)\n %build_a0_1_io = call ptr @sz_io_println(ptr %build_a0_1_ss)\n call void @sz_release(ptr %build_a0_1_ss)\n br label %build_aj_0_1\nbuild_aj_0_1:\n br label %build_merge_0\nbuild_merge_0:\n %build_phi = phi ptr [ %build_a0_0_io, %build_aj_0_0 ], [ %build_a0_1_io, %build_aj_0_1 ], [ null, %build_default_0 ]\n call void @sz_release(ptr %build_sc_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_phi, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"0\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n@.str2 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:2:3@79#0\\00\", align 1\n@.str3 = private unnamed_addr constant [21 x i8] c\"Main.scuzz:2:3@130#1\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_sc_arg0_0 = call ptr @sz_list_nil()\n %build_sc_arg0_b0 = call ptr @sz_box_i64(i64 7)\n %build_sc_arg0_1 = call ptr @sz_list_cons(ptr %build_sc_arg0_b0, ptr %build_sc_arg0_0)\n call void @sz_release(ptr %build_sc_arg0_0)\n call void @sz_release(ptr %build_sc_arg0_b0)\n %build_sc_v = call ptr @sz_list_head_opt(ptr %build_sc_arg0_1)\n call void @sz_release(ptr %build_sc_arg0_1)\n br label %build_try_0_0\nbuild_try_0_0:\n %build_p0_0_tg1 = call i32 @sz_adt_tag(ptr %build_sc_v)\n %build_p0_0_eq1 = icmp eq i32 %build_p0_0_tg1, 1\n br i1 %build_p0_0_eq1, label %build_p0_0_m1, label %build_try_0_1\nbuild_p0_0_m1:\n %build_p0_0_f1_pl = call ptr @sz_adt_payload(ptr %build_sc_v)\n %build_p0_0_f1_b0 = call i64 @sz_unbox_i64(ptr %build_p0_0_f1_pl)\n br label %build_ok_0_0\nbuild_try_0_1:\n %build_p0_1_tg2 = call i32 @sz_adt_tag(ptr %build_sc_v)\n %build_p0_1_eq2 = icmp eq i32 %build_p0_1_tg2, 0\n br i1 %build_p0_1_eq2, label %build_p0_1_m2, label %build_default_0\nbuild_p0_1_m2:\n br label %build_ok_0_1\nbuild_default_0:\n br label %build_merge_0\nbuild_ok_0_0:\n %build_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_cv0g0)\n %build_a0_0_e_v = call ptr @sz_string_from_int(i64 %build_p0_0_f1_b0)\n %build_a0_0_io = call ptr @sz_io_println(ptr %build_a0_0_e_v)\n call void @sz_release(ptr %build_a0_0_e_v)\n br label %build_aj_0_0\nbuild_aj_0_0:\n br label %build_merge_0\nbuild_ok_0_1:\n %build_cv1g0 = getelementptr inbounds [21 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_cv1g0)\n %build_a0_1_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_a0_1_ss = call ptr @sz_string_lit(ptr %build_a0_1_gep)\n %build_a0_1_io = call ptr @sz_io_println(ptr %build_a0_1_ss)\n call void @sz_release(ptr %build_a0_1_ss)\n br label %build_aj_0_1\nbuild_aj_0_1:\n br label %build_merge_0\nbuild_merge_0:\n %build_phi = phi ptr [ %build_a0_0_io, %build_aj_0_0 ], [ %build_a0_1_io, %build_aj_0_1 ], [ null, %build_default_0 ]\n call void @sz_release(ptr %build_sc_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_phi, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcEmptyQ(): String = """@main def main: IO[Unit] = @@ -238,13 +238,13 @@ def srcTok(): String = "enum Tok:\n case Eof\n case Ident(s: String)\ndef show(t: Tok): String =\n t match {\n case Tok.Ident(s) => s\n case Tok.Eof => \"e\"\n }\n@main def main: IO[Unit] =\n IO.println(show(Tok.Ident(\"x\")))\n" def wantTok(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"e\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str2 = private unnamed_addr constant [16 x i8] c\"Main.scuzz:10:3\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:4:5\\00\", align 1\n@.str4 = private unnamed_addr constant [21 x i8] c\"Main.scuzz:4:5@109#0\\00\", align 1\n@.str5 = private unnamed_addr constant [21 x i8] c\"Main.scuzz:4:5@131#1\\00\", align 1\n\ndefine internal ptr @sz_user_Main_show(ptr %t) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tg1 = call i32 @sz_adt_tag(ptr %t)\n %body_p0_0_eq1 = icmp eq i32 %body_p0_0_tg1, 1\n br i1 %body_p0_0_eq1, label %body_p0_0_m1, label %body_try_0_1\nbody_p0_0_m1:\n %body_p0_0_f1_pl = call ptr @sz_adt_payload(ptr %t)\n br label %body_ok_0_0\nbody_try_0_1:\n %body_p0_1_tg2 = call i32 @sz_adt_tag(ptr %t)\n %body_p0_1_eq2 = icmp eq i32 %body_p0_1_tg2, 0\n br i1 %body_p0_1_eq2, label %body_p0_1_m2, label %body_default_0\nbody_p0_1_m2:\n br label %body_ok_0_1\nbody_default_0:\n br label %body_merge_0\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [21 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n call void @sz_retain(ptr %body_p0_0_f1_pl)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_ok_0_1:\n %body_cv1g0 = getelementptr inbounds [21 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv1g0)\n %body_a0_1_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %body_a0_1_s = call ptr @sz_string_from_cstr(ptr %body_a0_1_gep)\n br label %body_aj_0_1\nbody_aj_0_1:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi ptr [ %body_p0_0_f1_pl, %body_aj_0_0 ], [ %body_a0_1_s, %body_aj_0_1 ], [ null, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret ptr %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [16 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_ap_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg0_ap_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_ap_gep)\n %build_e_arg0_adt = call ptr @sz_adt_new(i32 1, ptr %build_e_arg0_ap_s)\n call void @sz_release(ptr %build_e_arg0_ap_s)\n %build_e_v = call ptr @sz_user_Main_show(ptr %build_e_arg0_adt)\n call void @sz_release(ptr %build_e_arg0_adt)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"e\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str2 = private unnamed_addr constant [16 x i8] c\"Main.scuzz:10:3\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:4:5\\00\", align 1\n@.str4 = private unnamed_addr constant [21 x i8] c\"Main.scuzz:4:5@109#0\\00\", align 1\n@.str5 = private unnamed_addr constant [21 x i8] c\"Main.scuzz:4:5@131#1\\00\", align 1\n\ndefine internal ptr @sz_user_Main_show(ptr %t) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tg1 = call i32 @sz_adt_tag(ptr %t)\n %body_p0_0_eq1 = icmp eq i32 %body_p0_0_tg1, 1\n br i1 %body_p0_0_eq1, label %body_p0_0_m1, label %body_try_0_1\nbody_p0_0_m1:\n %body_p0_0_f1_pl = call ptr @sz_adt_payload(ptr %t)\n br label %body_ok_0_0\nbody_try_0_1:\n %body_p0_1_tg2 = call i32 @sz_adt_tag(ptr %t)\n %body_p0_1_eq2 = icmp eq i32 %body_p0_1_tg2, 0\n br i1 %body_p0_1_eq2, label %body_p0_1_m2, label %body_default_0\nbody_p0_1_m2:\n br label %body_ok_0_1\nbody_default_0:\n br label %body_merge_0\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [21 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n call void @sz_retain(ptr %body_p0_0_f1_pl)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_ok_0_1:\n %body_cv1g0 = getelementptr inbounds [21 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv1g0)\n %body_a0_1_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %body_a0_1_s = call ptr @sz_string_lit(ptr %body_a0_1_gep)\n br label %body_aj_0_1\nbody_aj_0_1:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi ptr [ %body_p0_0_f1_pl, %body_aj_0_0 ], [ %body_a0_1_s, %body_aj_0_1 ], [ null, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret ptr %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [16 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_ap_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg0_ap_s = call ptr @sz_string_lit(ptr %build_e_arg0_ap_gep)\n %build_e_arg0_adt = call ptr @sz_adt_new(i32 1, ptr %build_e_arg0_ap_s)\n call void @sz_release(ptr %build_e_arg0_ap_s)\n %build_e_v = call ptr @sz_user_Main_show(ptr %build_e_arg0_adt)\n call void @sz_release(ptr %build_e_arg0_adt)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcKw(): String = "def kw(s: String): String =\n s match {\n case \"package\" => \"p\"\n case _ => \"i\"\n }\n@main def main: IO[Unit] =\n IO.println(kw(\"package\"))\n" def wantKw(): String = - "@.str0 = private unnamed_addr constant [8 x i8] c\"package\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"p\\00\", align 1\n@.str2 = private unnamed_addr constant [2 x i8] c\"i\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:7:3\\00\", align 1\n@.str4 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str5 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@62#0\\00\", align 1\n@.str6 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@80#1\\00\", align 1\n\ndefine internal ptr @sz_user_Main_kw(ptr %s) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_sgep1 = getelementptr inbounds [8 x i8], ptr @.str0, i64 0, i64 0\n %body_p0_0_slit1 = call ptr @sz_string_from_cstr(ptr %body_p0_0_sgep1)\n %body_p0_0_seqi1 = call i32 @sz_string_eq(ptr %s, ptr %body_p0_0_slit1)\n call void @sz_release(ptr %body_p0_0_slit1)\n %body_p0_0_seq1 = icmp ne i32 %body_p0_0_seqi1, 0\n br i1 %body_p0_0_seq1, label %body_ok_0_0, label %body_try_0_1\nbody_try_0_1:\n br label %body_ok_0_1\nbody_default_0:\n unreachable\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n %body_a0_0_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %body_a0_0_s = call ptr @sz_string_from_cstr(ptr %body_a0_0_gep)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_ok_0_1:\n %body_cv1g0 = getelementptr inbounds [20 x i8], ptr @.str6, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv1g0)\n %body_a0_1_gep = getelementptr inbounds [2 x i8], ptr @.str2, i64 0, i64 0\n %body_a0_1_s = call ptr @sz_string_from_cstr(ptr %body_a0_1_gep)\n br label %body_aj_0_1\nbody_aj_0_1:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi ptr [ %body_a0_0_s, %body_aj_0_0 ], [ %body_a0_1_s, %body_aj_0_1 ]\n call void @sz_panic_pop_src()\n ret ptr %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_gep = getelementptr inbounds [8 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_gep)\n %build_e_v = call ptr @sz_user_Main_kw(ptr %build_e_arg0_s)\n call void @sz_release(ptr %build_e_arg0_s)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [8 x i8] c\"package\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"p\\00\", align 1\n@.str2 = private unnamed_addr constant [2 x i8] c\"i\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:7:3\\00\", align 1\n@.str4 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str5 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@62#0\\00\", align 1\n@.str6 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@80#1\\00\", align 1\n\ndefine internal ptr @sz_user_Main_kw(ptr %s) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_sgep1 = getelementptr inbounds [8 x i8], ptr @.str0, i64 0, i64 0\n %body_p0_0_slit1 = call ptr @sz_string_lit(ptr %body_p0_0_sgep1)\n %body_p0_0_seqi1 = call i32 @sz_string_eq(ptr %s, ptr %body_p0_0_slit1)\n call void @sz_release(ptr %body_p0_0_slit1)\n %body_p0_0_seq1 = icmp ne i32 %body_p0_0_seqi1, 0\n br i1 %body_p0_0_seq1, label %body_ok_0_0, label %body_try_0_1\nbody_try_0_1:\n br label %body_ok_0_1\nbody_default_0:\n unreachable\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n %body_a0_0_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %body_a0_0_s = call ptr @sz_string_lit(ptr %body_a0_0_gep)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_ok_0_1:\n %body_cv1g0 = getelementptr inbounds [20 x i8], ptr @.str6, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv1g0)\n %body_a0_1_gep = getelementptr inbounds [2 x i8], ptr @.str2, i64 0, i64 0\n %body_a0_1_s = call ptr @sz_string_lit(ptr %body_a0_1_gep)\n br label %body_aj_0_1\nbody_aj_0_1:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi ptr [ %body_a0_0_s, %body_aj_0_0 ], [ %body_a0_1_s, %body_aj_0_1 ]\n call void @sz_panic_pop_src()\n ret ptr %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_gep = getelementptr inbounds [8 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_s = call ptr @sz_string_lit(ptr %build_e_arg0_gep)\n %build_e_v = call ptr @sz_user_Main_kw(ptr %build_e_arg0_s)\n call void @sz_release(ptr %build_e_arg0_s)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcPunct(): String = """def p(c: Int): Int = @@ -263,31 +263,31 @@ def srcCons(): String = "enum Tok:\n case Eof\n case Ident(s: String)\n@main def main: IO[Unit] =\n IO.println(Str.fromInt(List.len(Tok.Ident(\"x\") :: [Tok.Eof])))\n" def wantCons(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:5:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_arg0_ap_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_arg0_ap_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_arg0_arg0_ap_gep)\n %build_e_arg0_arg0_arg0_adt = call ptr @sz_adt_new(i32 1, ptr %build_e_arg0_arg0_arg0_ap_s)\n call void @sz_release(ptr %build_e_arg0_arg0_arg0_ap_s)\n %build_e_arg0_arg0_arg1_0 = call ptr @sz_list_nil()\n %build_e_arg0_arg0_arg1_e0_adt = call ptr @sz_adt_new(i32 0, ptr null)\n %build_e_arg0_arg0_arg1_1 = call ptr @sz_list_cons(ptr %build_e_arg0_arg0_arg1_e0_adt, ptr %build_e_arg0_arg0_arg1_0)\n call void @sz_release(ptr %build_e_arg0_arg0_arg1_0)\n call void @sz_release(ptr %build_e_arg0_arg0_arg1_e0_adt)\n %build_e_arg0_arg0_v = call ptr @sz_list_cons(ptr %build_e_arg0_arg0_arg0_adt, ptr %build_e_arg0_arg0_arg1_1)\n call void @sz_release(ptr %build_e_arg0_arg0_arg0_adt)\n call void @sz_release(ptr %build_e_arg0_arg0_arg1_1)\n %build_e_arg0_v = call i64 @sz_list_len(ptr %build_e_arg0_arg0_v)\n call void @sz_release(ptr %build_e_arg0_arg0_v)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:5:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_arg0_ap_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_arg0_ap_s = call ptr @sz_string_lit(ptr %build_e_arg0_arg0_arg0_ap_gep)\n %build_e_arg0_arg0_arg0_adt = call ptr @sz_adt_new(i32 1, ptr %build_e_arg0_arg0_arg0_ap_s)\n call void @sz_release(ptr %build_e_arg0_arg0_arg0_ap_s)\n %build_e_arg0_arg0_arg1_0 = call ptr @sz_list_nil()\n %build_e_arg0_arg0_arg1_e0_adt = call ptr @sz_adt_new(i32 0, ptr null)\n %build_e_arg0_arg0_arg1_1 = call ptr @sz_list_cons(ptr %build_e_arg0_arg0_arg1_e0_adt, ptr %build_e_arg0_arg0_arg1_0)\n call void @sz_release(ptr %build_e_arg0_arg0_arg1_0)\n call void @sz_release(ptr %build_e_arg0_arg0_arg1_e0_adt)\n %build_e_arg0_arg0_v = call ptr @sz_list_cons(ptr %build_e_arg0_arg0_arg0_adt, ptr %build_e_arg0_arg0_arg1_1)\n call void @sz_release(ptr %build_e_arg0_arg0_arg0_adt)\n call void @sz_release(ptr %build_e_arg0_arg0_arg1_1)\n %build_e_arg0_v = call i64 @sz_list_len(ptr %build_e_arg0_arg0_v)\n call void @sz_release(ptr %build_e_arg0_arg0_v)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcCh(): String = "@main def main: IO[Unit] =\n IO.println(Str.fromInt(Str.charAt(\"ab\", 0)))\n" def wantCh(): String = - "@.str0 = private unnamed_addr constant [3 x i8] c\"ab\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_gep = getelementptr inbounds [3 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_arg0_gep)\n %build_e_arg0_v = call i64 @sz_string_uchar_at(ptr %build_e_arg0_arg0_s, i64 0)\n call void @sz_release(ptr %build_e_arg0_arg0_s)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [3 x i8] c\"ab\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_gep = getelementptr inbounds [3 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_s = call ptr @sz_string_lit(ptr %build_e_arg0_arg0_gep)\n %build_e_arg0_v = call i64 @sz_string_uchar_at(ptr %build_e_arg0_arg0_s, i64 0)\n call void @sz_release(ptr %build_e_arg0_arg0_s)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcSl(): String = "@main def main: IO[Unit] =\n IO.println(Str.slice(\"ab\", 0, 1))\n" def wantSl(): String = - "@.str0 = private unnamed_addr constant [3 x i8] c\"ab\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_gep = getelementptr inbounds [3 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_gep)\n %build_e_v = call ptr @sz_string_uslice(ptr %build_e_arg0_s, i64 0, i64 1)\n call void @sz_release(ptr %build_e_arg0_s)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [3 x i8] c\"ab\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_gep = getelementptr inbounds [3 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_s = call ptr @sz_string_lit(ptr %build_e_arg0_gep)\n %build_e_v = call ptr @sz_string_uslice(ptr %build_e_arg0_s, i64 0, i64 1)\n call void @sz_release(ptr %build_e_arg0_s)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcBld(): String = "@main def main: IO[Unit] =\n IO.println(Builder.result(Builder.append(Builder.empty(), \"x\")))\n" def wantBld(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_v = call ptr @sz_builder_new()\n %build_e_arg0_arg1_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg1_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_arg1_gep)\n %build_e_arg0_v = call ptr @sz_builder_append(ptr %build_e_arg0_arg0_v, ptr %build_e_arg0_arg1_s)\n call void @sz_release(ptr %build_e_arg0_arg0_v)\n call void @sz_release(ptr %build_e_arg0_arg1_s)\n %build_e_v = call ptr @sz_builder_result(ptr %build_e_arg0_v)\n call void @sz_release(ptr %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_v = call ptr @sz_builder_new()\n %build_e_arg0_arg1_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg1_s = call ptr @sz_string_lit(ptr %build_e_arg0_arg1_gep)\n %build_e_arg0_v = call ptr @sz_builder_append(ptr %build_e_arg0_arg0_v, ptr %build_e_arg0_arg1_s)\n call void @sz_release(ptr %build_e_arg0_arg0_v)\n call void @sz_release(ptr %build_e_arg0_arg1_s)\n %build_e_v = call ptr @sz_builder_result(ptr %build_e_arg0_v)\n call void @sz_release(ptr %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcToInt(): String = "@main def main: IO[Unit] =\n IO.println(Str.fromInt(Str.toInt(\"7\", 0)))\n" def wantToInt(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"7\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_arg0_gep)\n %build_e_arg0_v = call i64 @sz_string_to_int(ptr %build_e_arg0_arg0_s, i64 0)\n call void @sz_release(ptr %build_e_arg0_arg0_s)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"7\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_s = call ptr @sz_string_lit(ptr %build_e_arg0_arg0_gep)\n %build_e_arg0_v = call i64 @sz_string_to_int(ptr %build_e_arg0_arg0_s, i64 0)\n call void @sz_release(ptr %build_e_arg0_arg0_s)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcPair(): String = """def f(p: (Builder, Int)): Int = @@ -336,7 +336,7 @@ def srcModFiles(): List[(String, String)] = ("A", srcA()) :: ("B", srcB()) :: ("Main", srcModMain()) :: [] def wantMod(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [6 x i8] c\"guard\\00\", align 1\n@.str3 = private unnamed_addr constant [16 x i8] c\"Main.scuzz:1:28\\00\", align 1\n@.str4 = private unnamed_addr constant [12 x i8] c\"A.scuzz:1:5\\00\", align 1\n@.str5 = private unnamed_addr constant [12 x i8] c\"B.scuzz:1:5\\00\", align 1\n\ndefine internal ptr @sz_user_A_tag() {\n.entry:\n %szlocg0 = getelementptr inbounds [12 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %body_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %body_s = call ptr @sz_string_from_cstr(ptr %body_gep)\n call void @sz_panic_pop_src()\n ret ptr %body_s\n}\n\ndefine internal ptr @sz_user_B_tag() {\n.entry:\n %szlocg0 = getelementptr inbounds [12 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %body_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %body_s = call ptr @sz_string_from_cstr(ptr %body_gep)\n call void @sz_panic_pop_src()\n ret ptr %body_s\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [16 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_v = call ptr @sz_user_A_tag()\n %build_e_arg1_v = call ptr @sz_user_B_tag()\n %build_e_v = call ptr @sz_string_concat(ptr %build_e_arg0_v, ptr %build_e_arg1_v)\n call void @sz_release(ptr %build_e_arg0_v)\n call void @sz_release(ptr %build_e_arg1_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [6 x i8] c\"guard\\00\", align 1\n@.str3 = private unnamed_addr constant [16 x i8] c\"Main.scuzz:1:28\\00\", align 1\n@.str4 = private unnamed_addr constant [12 x i8] c\"A.scuzz:1:5\\00\", align 1\n@.str5 = private unnamed_addr constant [12 x i8] c\"B.scuzz:1:5\\00\", align 1\n\ndefine internal ptr @sz_user_A_tag() {\n.entry:\n %szlocg0 = getelementptr inbounds [12 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %body_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %body_s = call ptr @sz_string_lit(ptr %body_gep)\n call void @sz_panic_pop_src()\n ret ptr %body_s\n}\n\ndefine internal ptr @sz_user_B_tag() {\n.entry:\n %szlocg0 = getelementptr inbounds [12 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %body_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %body_s = call ptr @sz_string_lit(ptr %body_gep)\n call void @sz_panic_pop_src()\n ret ptr %body_s\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [16 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_v = call ptr @sz_user_A_tag()\n %build_e_arg1_v = call ptr @sz_user_B_tag()\n %build_e_v = call ptr @sz_string_concat(ptr %build_e_arg0_v, ptr %build_e_arg1_v)\n call void @sz_release(ptr %build_e_arg0_v)\n call void @sz_release(ptr %build_e_arg1_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def irDiffFiles(files: List[(String, String)], want: String): Bool = Emit.emitFiles(files) == want @@ -366,7 +366,7 @@ def srcCopyFld(): String = "record Box(s: String, t: String)\ndef copy(b: Box): Box =\n Box(b.s, b.t)\ndef get(b: Box): String =\n b.s\n@main def main: IO[Unit] =\n IO.println(get(copy(Box(\"a\", \"b\"))))\n" def wantCopyFld(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:7:3\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:5\\00\", align 1\n@.str4 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:4:5\\00\", align 1\n\ndefine internal ptr @sz_user_Main_copy(ptr %b) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_arg0_try_0_0\nbody_arg0_try_0_0:\n %body_arg0_p0_0_tg1 = call i32 @sz_adt_tag(ptr %b)\n %body_arg0_p0_0_eq1 = icmp eq i32 %body_arg0_p0_0_tg1, 0\n br i1 %body_arg0_p0_0_eq1, label %body_arg0_p0_0_m1, label %body_arg0_default_0\nbody_arg0_p0_0_m1:\n %body_arg0_p0_0_f1_pl = call ptr @sz_adt_payload(ptr %b)\n %body_arg0_p0_0_f1_c0 = call ptr @sz_list_at(ptr %body_arg0_p0_0_f1_pl, i64 0)\n %body_arg0_p0_0_f1_c1 = call ptr @sz_list_at(ptr %body_arg0_p0_0_f1_pl, i64 1)\n br label %body_arg0_p0_0_n1_0\nbody_arg0_p0_0_n1_0:\n br label %body_arg0_ok_0_0\nbody_arg0_default_0:\n br label %body_arg0_merge_0\nbody_arg0_ok_0_0:\n call void @sz_retain(ptr %body_arg0_p0_0_f1_c0)\n br label %body_arg0_aj_0_0\nbody_arg0_aj_0_0:\n br label %body_arg0_merge_0\nbody_arg0_merge_0:\n %body_arg0_phi = phi ptr [ %body_arg0_p0_0_f1_c0, %body_arg0_aj_0_0 ], [ null, %body_arg0_default_0 ]\n br label %body_arg1_try_0_0\nbody_arg1_try_0_0:\n %body_arg1_p0_0_tg1 = call i32 @sz_adt_tag(ptr %b)\n %body_arg1_p0_0_eq1 = icmp eq i32 %body_arg1_p0_0_tg1, 0\n br i1 %body_arg1_p0_0_eq1, label %body_arg1_p0_0_m1, label %body_arg1_default_0\nbody_arg1_p0_0_m1:\n %body_arg1_p0_0_f1_pl = call ptr @sz_adt_payload(ptr %b)\n %body_arg1_p0_0_f1_c0 = call ptr @sz_list_at(ptr %body_arg1_p0_0_f1_pl, i64 0)\n %body_arg1_p0_0_f1_c1 = call ptr @sz_list_at(ptr %body_arg1_p0_0_f1_pl, i64 1)\n br label %body_arg1_p0_0_n1_0\nbody_arg1_p0_0_n1_0:\n br label %body_arg1_ok_0_0\nbody_arg1_default_0:\n br label %body_arg1_merge_0\nbody_arg1_ok_0_0:\n call void @sz_retain(ptr %body_arg1_p0_0_f1_c1)\n br label %body_arg1_aj_0_0\nbody_arg1_aj_0_0:\n br label %body_arg1_merge_0\nbody_arg1_merge_0:\n %body_arg1_phi = phi ptr [ %body_arg1_p0_0_f1_c1, %body_arg1_aj_0_0 ], [ null, %body_arg1_default_0 ]\n %body_pl0 = call ptr @sz_list_nil()\n %body_pl1 = call ptr @sz_list_cons(ptr %body_arg1_phi, ptr %body_pl0)\n call void @sz_release(ptr %body_pl0)\n call void @sz_release(ptr %body_arg1_phi)\n %body_pl2 = call ptr @sz_list_cons(ptr %body_arg0_phi, ptr %body_pl1)\n call void @sz_release(ptr %body_pl1)\n call void @sz_release(ptr %body_arg0_phi)\n %body_adt = call ptr @sz_adt_new(i32 0, ptr %body_pl2)\n call void @sz_release(ptr %body_pl2)\n call void @sz_panic_pop_src()\n ret ptr %body_adt\n}\n\ndefine internal ptr @sz_user_Main_get(ptr %b) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tg1 = call i32 @sz_adt_tag(ptr %b)\n %body_p0_0_eq1 = icmp eq i32 %body_p0_0_tg1, 0\n br i1 %body_p0_0_eq1, label %body_p0_0_m1, label %body_default_0\nbody_p0_0_m1:\n %body_p0_0_f1_pl = call ptr @sz_adt_payload(ptr %b)\n %body_p0_0_f1_c0 = call ptr @sz_list_at(ptr %body_p0_0_f1_pl, i64 0)\n %body_p0_0_f1_c1 = call ptr @sz_list_at(ptr %body_p0_0_f1_pl, i64 1)\n br label %body_p0_0_n1_0\nbody_p0_0_n1_0:\n br label %body_ok_0_0\nbody_default_0:\n br label %body_merge_0\nbody_ok_0_0:\n call void @sz_retain(ptr %body_p0_0_f1_c0)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi ptr [ %body_p0_0_f1_c0, %body_aj_0_0 ], [ null, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret ptr %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_arg0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_arg0_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_arg0_arg0_gep)\n %build_e_arg0_arg0_arg1_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg0_arg0_arg1_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_arg0_arg1_gep)\n %build_e_arg0_arg0_pl0 = call ptr @sz_list_nil()\n %build_e_arg0_arg0_pl1 = call ptr @sz_list_cons(ptr %build_e_arg0_arg0_arg1_s, ptr %build_e_arg0_arg0_pl0)\n call void @sz_release(ptr %build_e_arg0_arg0_pl0)\n call void @sz_release(ptr %build_e_arg0_arg0_arg1_s)\n %build_e_arg0_arg0_pl2 = call ptr @sz_list_cons(ptr %build_e_arg0_arg0_arg0_s, ptr %build_e_arg0_arg0_pl1)\n call void @sz_release(ptr %build_e_arg0_arg0_pl1)\n call void @sz_release(ptr %build_e_arg0_arg0_arg0_s)\n %build_e_arg0_arg0_adt = call ptr @sz_adt_new(i32 0, ptr %build_e_arg0_arg0_pl2)\n call void @sz_release(ptr %build_e_arg0_arg0_pl2)\n %build_e_arg0_v = call ptr @sz_user_Main_copy(ptr %build_e_arg0_arg0_adt)\n call void @sz_release(ptr %build_e_arg0_arg0_adt)\n %build_e_v = call ptr @sz_user_Main_get(ptr %build_e_arg0_v)\n call void @sz_release(ptr %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:7:3\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:5\\00\", align 1\n@.str4 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:4:5\\00\", align 1\n\ndefine internal ptr @sz_user_Main_copy(ptr %b) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_arg0_try_0_0\nbody_arg0_try_0_0:\n %body_arg0_p0_0_tg1 = call i32 @sz_adt_tag(ptr %b)\n %body_arg0_p0_0_eq1 = icmp eq i32 %body_arg0_p0_0_tg1, 0\n br i1 %body_arg0_p0_0_eq1, label %body_arg0_p0_0_m1, label %body_arg0_default_0\nbody_arg0_p0_0_m1:\n %body_arg0_p0_0_f1_pl = call ptr @sz_adt_payload(ptr %b)\n %body_arg0_p0_0_f1_c0 = call ptr @sz_list_at(ptr %body_arg0_p0_0_f1_pl, i64 0)\n %body_arg0_p0_0_f1_c1 = call ptr @sz_list_at(ptr %body_arg0_p0_0_f1_pl, i64 1)\n br label %body_arg0_p0_0_n1_0\nbody_arg0_p0_0_n1_0:\n br label %body_arg0_ok_0_0\nbody_arg0_default_0:\n br label %body_arg0_merge_0\nbody_arg0_ok_0_0:\n call void @sz_retain(ptr %body_arg0_p0_0_f1_c0)\n br label %body_arg0_aj_0_0\nbody_arg0_aj_0_0:\n br label %body_arg0_merge_0\nbody_arg0_merge_0:\n %body_arg0_phi = phi ptr [ %body_arg0_p0_0_f1_c0, %body_arg0_aj_0_0 ], [ null, %body_arg0_default_0 ]\n br label %body_arg1_try_0_0\nbody_arg1_try_0_0:\n %body_arg1_p0_0_tg1 = call i32 @sz_adt_tag(ptr %b)\n %body_arg1_p0_0_eq1 = icmp eq i32 %body_arg1_p0_0_tg1, 0\n br i1 %body_arg1_p0_0_eq1, label %body_arg1_p0_0_m1, label %body_arg1_default_0\nbody_arg1_p0_0_m1:\n %body_arg1_p0_0_f1_pl = call ptr @sz_adt_payload(ptr %b)\n %body_arg1_p0_0_f1_c0 = call ptr @sz_list_at(ptr %body_arg1_p0_0_f1_pl, i64 0)\n %body_arg1_p0_0_f1_c1 = call ptr @sz_list_at(ptr %body_arg1_p0_0_f1_pl, i64 1)\n br label %body_arg1_p0_0_n1_0\nbody_arg1_p0_0_n1_0:\n br label %body_arg1_ok_0_0\nbody_arg1_default_0:\n br label %body_arg1_merge_0\nbody_arg1_ok_0_0:\n call void @sz_retain(ptr %body_arg1_p0_0_f1_c1)\n br label %body_arg1_aj_0_0\nbody_arg1_aj_0_0:\n br label %body_arg1_merge_0\nbody_arg1_merge_0:\n %body_arg1_phi = phi ptr [ %body_arg1_p0_0_f1_c1, %body_arg1_aj_0_0 ], [ null, %body_arg1_default_0 ]\n %body_pl0 = call ptr @sz_list_nil()\n %body_pl1 = call ptr @sz_list_cons(ptr %body_arg1_phi, ptr %body_pl0)\n call void @sz_release(ptr %body_pl0)\n call void @sz_release(ptr %body_arg1_phi)\n %body_pl2 = call ptr @sz_list_cons(ptr %body_arg0_phi, ptr %body_pl1)\n call void @sz_release(ptr %body_pl1)\n call void @sz_release(ptr %body_arg0_phi)\n %body_adt = call ptr @sz_adt_new(i32 0, ptr %body_pl2)\n call void @sz_release(ptr %body_pl2)\n call void @sz_panic_pop_src()\n ret ptr %body_adt\n}\n\ndefine internal ptr @sz_user_Main_get(ptr %b) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tg1 = call i32 @sz_adt_tag(ptr %b)\n %body_p0_0_eq1 = icmp eq i32 %body_p0_0_tg1, 0\n br i1 %body_p0_0_eq1, label %body_p0_0_m1, label %body_default_0\nbody_p0_0_m1:\n %body_p0_0_f1_pl = call ptr @sz_adt_payload(ptr %b)\n %body_p0_0_f1_c0 = call ptr @sz_list_at(ptr %body_p0_0_f1_pl, i64 0)\n %body_p0_0_f1_c1 = call ptr @sz_list_at(ptr %body_p0_0_f1_pl, i64 1)\n br label %body_p0_0_n1_0\nbody_p0_0_n1_0:\n br label %body_ok_0_0\nbody_default_0:\n br label %body_merge_0\nbody_ok_0_0:\n call void @sz_retain(ptr %body_p0_0_f1_c0)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi ptr [ %body_p0_0_f1_c0, %body_aj_0_0 ], [ null, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret ptr %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_arg0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_arg0_s = call ptr @sz_string_lit(ptr %build_e_arg0_arg0_arg0_gep)\n %build_e_arg0_arg0_arg1_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg0_arg0_arg1_s = call ptr @sz_string_lit(ptr %build_e_arg0_arg0_arg1_gep)\n %build_e_arg0_arg0_pl0 = call ptr @sz_list_nil()\n %build_e_arg0_arg0_pl1 = call ptr @sz_list_cons(ptr %build_e_arg0_arg0_arg1_s, ptr %build_e_arg0_arg0_pl0)\n call void @sz_release(ptr %build_e_arg0_arg0_pl0)\n call void @sz_release(ptr %build_e_arg0_arg0_arg1_s)\n %build_e_arg0_arg0_pl2 = call ptr @sz_list_cons(ptr %build_e_arg0_arg0_arg0_s, ptr %build_e_arg0_arg0_pl1)\n call void @sz_release(ptr %build_e_arg0_arg0_pl1)\n call void @sz_release(ptr %build_e_arg0_arg0_arg0_s)\n %build_e_arg0_arg0_adt = call ptr @sz_adt_new(i32 0, ptr %build_e_arg0_arg0_pl2)\n call void @sz_release(ptr %build_e_arg0_arg0_pl2)\n %build_e_arg0_v = call ptr @sz_user_Main_copy(ptr %build_e_arg0_arg0_adt)\n call void @sz_release(ptr %build_e_arg0_arg0_adt)\n %build_e_v = call ptr @sz_user_Main_get(ptr %build_e_arg0_v)\n call void @sz_release(ptr %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcCopyRec(): String = """enum Color: @@ -457,7 +457,7 @@ def srcRep(): String = "@main def main: IO[Unit] =\n IO.println(Str.repeat(\"a\", 2))\n" def wantRep(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_gep)\n %build_e_v = call ptr @sz_string_repeat(ptr %build_e_arg0_s, i64 2)\n call void @sz_release(ptr %build_e_arg0_s)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_s = call ptr @sz_string_lit(ptr %build_e_arg0_gep)\n %build_e_v = call ptr @sz_string_repeat(ptr %build_e_arg0_s, i64 2)\n call void @sz_release(ptr %build_e_arg0_s)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcIfPtr(): String = """def f(b: Bool, acc: List[Int]): List[Int] = @@ -473,31 +473,31 @@ def srcTCons(): String = "def take(p: (String, Int), acc: List[String]): List[String] =\n p match {\n case (s, i) => s :: acc\n }\n@main def main: IO[Unit] =\n IO.println(Str.fromInt(List.len(take((\"a\", 1), []))))\n" def wantTCons(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:6:3\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str3 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@93#0\\00\", align 1\n\ndefine internal ptr @sz_user_Main_take(ptr %p, ptr %acc) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tl1 = call ptr @sz_pair_left(ptr %p)\n %body_p0_0_tr1 = call ptr @sz_pair_right(ptr %p)\n br label %body_p0_0_tn1\nbody_p0_0_tn1:\n %body_p0_0_tv1 = call i64 @sz_unbox_i64(ptr %body_p0_0_tr1)\n br label %body_ok_0_0\nbody_default_0:\n br label %body_merge_0\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n %body_a0_0_v = call ptr @sz_list_cons(ptr %body_p0_0_tl1, ptr %acc)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi ptr [ %body_a0_0_v, %body_aj_0_0 ], [ null, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret ptr %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_arg0_0_l_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_arg0_0_l_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_arg0_arg0_0_l_gep)\n %build_e_arg0_arg0_arg0_0_rb = call ptr @sz_box_i64(i64 1)\n %build_e_arg0_arg0_arg0_0_v = call ptr @sz_pair_new(ptr %build_e_arg0_arg0_arg0_0_l_s, ptr %build_e_arg0_arg0_arg0_0_rb)\n call void @sz_release(ptr %build_e_arg0_arg0_arg0_0_l_s)\n call void @sz_release(ptr %build_e_arg0_arg0_arg0_0_rb)\n %build_e_arg0_arg0_arg1_0 = call ptr @sz_list_nil()\n %build_e_arg0_arg0_v = call ptr @sz_user_Main_take(ptr %build_e_arg0_arg0_arg0_0_v, ptr %build_e_arg0_arg0_arg1_0)\n call void @sz_release(ptr %build_e_arg0_arg0_arg0_0_v)\n call void @sz_release(ptr %build_e_arg0_arg0_arg1_0)\n %build_e_arg0_v = call i64 @sz_list_len(ptr %build_e_arg0_arg0_v)\n call void @sz_release(ptr %build_e_arg0_arg0_v)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:6:3\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str3 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@93#0\\00\", align 1\n\ndefine internal ptr @sz_user_Main_take(ptr %p, ptr %acc) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tl1 = call ptr @sz_pair_left(ptr %p)\n %body_p0_0_tr1 = call ptr @sz_pair_right(ptr %p)\n br label %body_p0_0_tn1\nbody_p0_0_tn1:\n %body_p0_0_tv1 = call i64 @sz_unbox_i64(ptr %body_p0_0_tr1)\n br label %body_ok_0_0\nbody_default_0:\n br label %body_merge_0\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n %body_a0_0_v = call ptr @sz_list_cons(ptr %body_p0_0_tl1, ptr %acc)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi ptr [ %body_a0_0_v, %body_aj_0_0 ], [ null, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret ptr %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_arg0_0_l_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_arg0_0_l_s = call ptr @sz_string_lit(ptr %build_e_arg0_arg0_arg0_0_l_gep)\n %build_e_arg0_arg0_arg0_0_rb = call ptr @sz_box_i64(i64 1)\n %build_e_arg0_arg0_arg0_0_v = call ptr @sz_pair_new(ptr %build_e_arg0_arg0_arg0_0_l_s, ptr %build_e_arg0_arg0_arg0_0_rb)\n call void @sz_release(ptr %build_e_arg0_arg0_arg0_0_l_s)\n call void @sz_release(ptr %build_e_arg0_arg0_arg0_0_rb)\n %build_e_arg0_arg0_arg1_0 = call ptr @sz_list_nil()\n %build_e_arg0_arg0_v = call ptr @sz_user_Main_take(ptr %build_e_arg0_arg0_arg0_0_v, ptr %build_e_arg0_arg0_arg1_0)\n call void @sz_release(ptr %build_e_arg0_arg0_arg0_0_v)\n call void @sz_release(ptr %build_e_arg0_arg0_arg1_0)\n %build_e_arg0_v = call i64 @sz_list_len(ptr %build_e_arg0_arg0_v)\n call void @sz_release(ptr %build_e_arg0_arg0_v)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcTupPtr(): String = "def take(p: (String, String)): String =\n p match {\n case (a, b) => Str.concat(a, b)\n }\n@main def main: IO[Unit] =\n IO.println(take((\"a\", \"b\")))\n" def wantTupPtr(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:6:3\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@71#0\\00\", align 1\n\ndefine internal ptr @sz_user_Main_take(ptr %p) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tl1 = call ptr @sz_pair_left(ptr %p)\n %body_p0_0_tr1 = call ptr @sz_pair_right(ptr %p)\n br label %body_p0_0_tn1\nbody_p0_0_tn1:\n br label %body_ok_0_0\nbody_default_0:\n br label %body_merge_0\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n %body_a0_0_v = call ptr @sz_string_concat(ptr %body_p0_0_tl1, ptr %body_p0_0_tr1)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi ptr [ %body_a0_0_v, %body_aj_0_0 ], [ null, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret ptr %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_0_l_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_0_l_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_0_l_gep)\n %build_e_arg0_0_r_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg0_0_r_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_0_r_gep)\n %build_e_arg0_0_v = call ptr @sz_pair_new(ptr %build_e_arg0_0_l_s, ptr %build_e_arg0_0_r_s)\n call void @sz_release(ptr %build_e_arg0_0_l_s)\n call void @sz_release(ptr %build_e_arg0_0_r_s)\n %build_e_v = call ptr @sz_user_Main_take(ptr %build_e_arg0_0_v)\n call void @sz_release(ptr %build_e_arg0_0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:6:3\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@71#0\\00\", align 1\n\ndefine internal ptr @sz_user_Main_take(ptr %p) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tl1 = call ptr @sz_pair_left(ptr %p)\n %body_p0_0_tr1 = call ptr @sz_pair_right(ptr %p)\n br label %body_p0_0_tn1\nbody_p0_0_tn1:\n br label %body_ok_0_0\nbody_default_0:\n br label %body_merge_0\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n %body_a0_0_v = call ptr @sz_string_concat(ptr %body_p0_0_tl1, ptr %body_p0_0_tr1)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi ptr [ %body_a0_0_v, %body_aj_0_0 ], [ null, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret ptr %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_0_l_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_0_l_s = call ptr @sz_string_lit(ptr %build_e_arg0_0_l_gep)\n %build_e_arg0_0_r_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg0_0_r_s = call ptr @sz_string_lit(ptr %build_e_arg0_0_r_gep)\n %build_e_arg0_0_v = call ptr @sz_pair_new(ptr %build_e_arg0_0_l_s, ptr %build_e_arg0_0_r_s)\n call void @sz_release(ptr %build_e_arg0_0_l_s)\n call void @sz_release(ptr %build_e_arg0_0_r_s)\n %build_e_v = call ptr @sz_user_Main_take(ptr %build_e_arg0_0_v)\n call void @sz_release(ptr %build_e_arg0_0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcT3(): String = "def take(p: (String, String, Int)): Int =\n p match {\n case (a, b, i) => i\n }\n@main def main: IO[Unit] =\n IO.println(Str.fromInt(take((\"x\", \"y\", 7))))\n" def wantT3(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"y\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:6:3\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@76#0\\00\", align 1\n\ndefine internal i64 @sz_user_Main_take(ptr %p) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tl1 = call ptr @sz_pair_left(ptr %p)\n %body_p0_0_tr1 = call ptr @sz_pair_right(ptr %p)\n br label %body_p0_0_tn1\nbody_p0_0_tn1:\n %body_p0_0_n1_tl2 = call ptr @sz_pair_left(ptr %body_p0_0_tr1)\n %body_p0_0_n1_tr2 = call ptr @sz_pair_right(ptr %body_p0_0_tr1)\n br label %body_p0_0_n1_tn2\nbody_p0_0_n1_tn2:\n %body_p0_0_n1_tv2 = call i64 @sz_unbox_i64(ptr %body_p0_0_n1_tr2)\n br label %body_ok_0_0\nbody_default_0:\n %body_dflt = add i64 0, 0\n br label %body_merge_0\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi i64 [ %body_p0_0_n1_tv2, %body_aj_0_0 ], [ %body_dflt, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret i64 %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_h0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_h0_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_arg0_h0_gep)\n %build_e_arg0_arg0_1_l_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg0_arg0_1_l_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_arg0_1_l_gep)\n %build_e_arg0_arg0_1_rb = call ptr @sz_box_i64(i64 7)\n %build_e_arg0_arg0_1_v = call ptr @sz_pair_new(ptr %build_e_arg0_arg0_1_l_s, ptr %build_e_arg0_arg0_1_rb)\n call void @sz_release(ptr %build_e_arg0_arg0_1_l_s)\n call void @sz_release(ptr %build_e_arg0_arg0_1_rb)\n %build_e_arg0_arg0_p0_v = call ptr @sz_pair_new(ptr %build_e_arg0_arg0_h0_s, ptr %build_e_arg0_arg0_1_v)\n call void @sz_release(ptr %build_e_arg0_arg0_h0_s)\n call void @sz_release(ptr %build_e_arg0_arg0_1_v)\n %build_e_arg0_v = call i64 @sz_user_Main_take(ptr %build_e_arg0_arg0_p0_v)\n call void @sz_release(ptr %build_e_arg0_arg0_p0_v)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"y\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:6:3\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@76#0\\00\", align 1\n\ndefine internal i64 @sz_user_Main_take(ptr %p) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tl1 = call ptr @sz_pair_left(ptr %p)\n %body_p0_0_tr1 = call ptr @sz_pair_right(ptr %p)\n br label %body_p0_0_tn1\nbody_p0_0_tn1:\n %body_p0_0_n1_tl2 = call ptr @sz_pair_left(ptr %body_p0_0_tr1)\n %body_p0_0_n1_tr2 = call ptr @sz_pair_right(ptr %body_p0_0_tr1)\n br label %body_p0_0_n1_tn2\nbody_p0_0_n1_tn2:\n %body_p0_0_n1_tv2 = call i64 @sz_unbox_i64(ptr %body_p0_0_n1_tr2)\n br label %body_ok_0_0\nbody_default_0:\n %body_dflt = add i64 0, 0\n br label %body_merge_0\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi i64 [ %body_p0_0_n1_tv2, %body_aj_0_0 ], [ %body_dflt, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret i64 %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_h0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_h0_s = call ptr @sz_string_lit(ptr %build_e_arg0_arg0_h0_gep)\n %build_e_arg0_arg0_1_l_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg0_arg0_1_l_s = call ptr @sz_string_lit(ptr %build_e_arg0_arg0_1_l_gep)\n %build_e_arg0_arg0_1_rb = call ptr @sz_box_i64(i64 7)\n %build_e_arg0_arg0_1_v = call ptr @sz_pair_new(ptr %build_e_arg0_arg0_1_l_s, ptr %build_e_arg0_arg0_1_rb)\n call void @sz_release(ptr %build_e_arg0_arg0_1_l_s)\n call void @sz_release(ptr %build_e_arg0_arg0_1_rb)\n %build_e_arg0_arg0_p0_v = call ptr @sz_pair_new(ptr %build_e_arg0_arg0_h0_s, ptr %build_e_arg0_arg0_1_v)\n call void @sz_release(ptr %build_e_arg0_arg0_h0_s)\n call void @sz_release(ptr %build_e_arg0_arg0_1_v)\n %build_e_arg0_v = call i64 @sz_user_Main_take(ptr %build_e_arg0_arg0_p0_v)\n call void @sz_release(ptr %build_e_arg0_arg0_p0_v)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcIfTup(): String = "def take(p: (String, Int)): Int =\n p match {\n case (s, n) => n\n }\n@main def main: IO[Unit] =\n IO.println(Str.fromInt(take(if (true) (\"a\", 1) else (\"b\", 2))))\n" def wantIfTup(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:6:3\\00\", align 1\n@.str3 = private unnamed_addr constant [21 x i8] c\"Main.scuzz:6:3@128#t\\00\", align 1\n@.str4 = private unnamed_addr constant [21 x i8] c\"Main.scuzz:6:3@128#e\\00\", align 1\n@.str5 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str6 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@65#0\\00\", align 1\n\ndefine internal i64 @sz_user_Main_take(ptr %p) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tl1 = call ptr @sz_pair_left(ptr %p)\n %body_p0_0_tr1 = call ptr @sz_pair_right(ptr %p)\n br label %body_p0_0_tn1\nbody_p0_0_tn1:\n %body_p0_0_tv1 = call i64 @sz_unbox_i64(ptr %body_p0_0_tr1)\n br label %body_ok_0_0\nbody_default_0:\n %body_dflt = add i64 0, 0\n br label %body_merge_0\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str6, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi i64 [ %body_p0_0_tv1, %body_aj_0_0 ], [ %body_dflt, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret i64 %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_cmp = icmp ne i64 1, 0\n br i1 %build_e_arg0_arg0_cmp, label %build_e_arg0_arg0_then_0, label %build_e_arg0_arg0_else_0\nbuild_e_arg0_arg0_then_0:\n %build_e_arg0_arg0_cvtg0 = getelementptr inbounds [21 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_e_arg0_arg0_cvtg0)\n %build_e_arg0_arg0_t0_0_l_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_t0_0_l_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_arg0_t0_0_l_gep)\n %build_e_arg0_arg0_t0_0_rb = call ptr @sz_box_i64(i64 1)\n %build_e_arg0_arg0_t0_0_v = call ptr @sz_pair_new(ptr %build_e_arg0_arg0_t0_0_l_s, ptr %build_e_arg0_arg0_t0_0_rb)\n call void @sz_release(ptr %build_e_arg0_arg0_t0_0_l_s)\n call void @sz_release(ptr %build_e_arg0_arg0_t0_0_rb)\n br label %build_e_arg0_arg0_tj_0\nbuild_e_arg0_arg0_tj_0:\n br label %build_e_arg0_arg0_merge_0\nbuild_e_arg0_arg0_else_0:\n %build_e_arg0_arg0_cveg0 = getelementptr inbounds [21 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_e_arg0_arg0_cveg0)\n %build_e_arg0_arg0_e0_0_l_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg0_arg0_e0_0_l_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_arg0_e0_0_l_gep)\n %build_e_arg0_arg0_e0_0_rb = call ptr @sz_box_i64(i64 2)\n %build_e_arg0_arg0_e0_0_v = call ptr @sz_pair_new(ptr %build_e_arg0_arg0_e0_0_l_s, ptr %build_e_arg0_arg0_e0_0_rb)\n call void @sz_release(ptr %build_e_arg0_arg0_e0_0_l_s)\n call void @sz_release(ptr %build_e_arg0_arg0_e0_0_rb)\n br label %build_e_arg0_arg0_ej_0\nbuild_e_arg0_arg0_ej_0:\n br label %build_e_arg0_arg0_merge_0\nbuild_e_arg0_arg0_merge_0:\n %build_e_arg0_arg0_phi = phi ptr [ %build_e_arg0_arg0_t0_0_v, %build_e_arg0_arg0_tj_0 ], [ %build_e_arg0_arg0_e0_0_v, %build_e_arg0_arg0_ej_0 ]\n %build_e_arg0_v = call i64 @sz_user_Main_take(ptr %build_e_arg0_arg0_phi)\n call void @sz_release(ptr %build_e_arg0_arg0_phi)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:6:3\\00\", align 1\n@.str3 = private unnamed_addr constant [21 x i8] c\"Main.scuzz:6:3@128#t\\00\", align 1\n@.str4 = private unnamed_addr constant [21 x i8] c\"Main.scuzz:6:3@128#e\\00\", align 1\n@.str5 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str6 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@65#0\\00\", align 1\n\ndefine internal i64 @sz_user_Main_take(ptr %p) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tl1 = call ptr @sz_pair_left(ptr %p)\n %body_p0_0_tr1 = call ptr @sz_pair_right(ptr %p)\n br label %body_p0_0_tn1\nbody_p0_0_tn1:\n %body_p0_0_tv1 = call i64 @sz_unbox_i64(ptr %body_p0_0_tr1)\n br label %body_ok_0_0\nbody_default_0:\n %body_dflt = add i64 0, 0\n br label %body_merge_0\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str6, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi i64 [ %body_p0_0_tv1, %body_aj_0_0 ], [ %body_dflt, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret i64 %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_arg0_cmp = icmp ne i64 1, 0\n br i1 %build_e_arg0_arg0_cmp, label %build_e_arg0_arg0_then_0, label %build_e_arg0_arg0_else_0\nbuild_e_arg0_arg0_then_0:\n %build_e_arg0_arg0_cvtg0 = getelementptr inbounds [21 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_e_arg0_arg0_cvtg0)\n %build_e_arg0_arg0_t0_0_l_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_arg0_t0_0_l_s = call ptr @sz_string_lit(ptr %build_e_arg0_arg0_t0_0_l_gep)\n %build_e_arg0_arg0_t0_0_rb = call ptr @sz_box_i64(i64 1)\n %build_e_arg0_arg0_t0_0_v = call ptr @sz_pair_new(ptr %build_e_arg0_arg0_t0_0_l_s, ptr %build_e_arg0_arg0_t0_0_rb)\n call void @sz_release(ptr %build_e_arg0_arg0_t0_0_l_s)\n call void @sz_release(ptr %build_e_arg0_arg0_t0_0_rb)\n br label %build_e_arg0_arg0_tj_0\nbuild_e_arg0_arg0_tj_0:\n br label %build_e_arg0_arg0_merge_0\nbuild_e_arg0_arg0_else_0:\n %build_e_arg0_arg0_cveg0 = getelementptr inbounds [21 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_e_arg0_arg0_cveg0)\n %build_e_arg0_arg0_e0_0_l_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg0_arg0_e0_0_l_s = call ptr @sz_string_lit(ptr %build_e_arg0_arg0_e0_0_l_gep)\n %build_e_arg0_arg0_e0_0_rb = call ptr @sz_box_i64(i64 2)\n %build_e_arg0_arg0_e0_0_v = call ptr @sz_pair_new(ptr %build_e_arg0_arg0_e0_0_l_s, ptr %build_e_arg0_arg0_e0_0_rb)\n call void @sz_release(ptr %build_e_arg0_arg0_e0_0_l_s)\n call void @sz_release(ptr %build_e_arg0_arg0_e0_0_rb)\n br label %build_e_arg0_arg0_ej_0\nbuild_e_arg0_arg0_ej_0:\n br label %build_e_arg0_arg0_merge_0\nbuild_e_arg0_arg0_merge_0:\n %build_e_arg0_arg0_phi = phi ptr [ %build_e_arg0_arg0_t0_0_v, %build_e_arg0_arg0_tj_0 ], [ %build_e_arg0_arg0_e0_0_v, %build_e_arg0_arg0_ej_0 ]\n %build_e_arg0_v = call i64 @sz_user_Main_take(ptr %build_e_arg0_arg0_phi)\n call void @sz_release(ptr %build_e_arg0_arg0_phi)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcNestTup(): String = "def take(p: ((String, String), String)): String =\n p match {\n case ((a, b), c) => Str.concat(a, Str.concat(b, c))\n }\n@main def main: IO[Unit] =\n IO.println(take(((\"x\", \"y\"), \"z\")))\n" def wantNestTup(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"y\\00\", align 1\n@.str2 = private unnamed_addr constant [2 x i8] c\"z\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:6:3\\00\", align 1\n@.str4 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str5 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@86#0\\00\", align 1\n\ndefine internal ptr @sz_user_Main_take(ptr %p) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tl1 = call ptr @sz_pair_left(ptr %p)\n %body_p0_0_tr1 = call ptr @sz_pair_right(ptr %p)\n br label %body_p0_0_tn1\nbody_p0_0_tn1:\n %body_p0_0_tl1_tl1 = call ptr @sz_pair_left(ptr %body_p0_0_tl1)\n %body_p0_0_tl1_tr1 = call ptr @sz_pair_right(ptr %body_p0_0_tl1)\n br label %body_p0_0_tl1_tn1\nbody_p0_0_tl1_tn1:\n br label %body_ok_0_0\nbody_default_0:\n br label %body_merge_0\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n %body_a0_0_arg1_v = call ptr @sz_string_concat(ptr %body_p0_0_tl1_tr1, ptr %body_p0_0_tr1)\n %body_a0_0_v = call ptr @sz_string_concat(ptr %body_p0_0_tl1_tl1, ptr %body_a0_0_arg1_v)\n call void @sz_release(ptr %body_a0_0_arg1_v)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi ptr [ %body_a0_0_v, %body_aj_0_0 ], [ null, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret ptr %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_0_l_0_l_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_0_l_0_l_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_0_l_0_l_gep)\n %build_e_arg0_0_l_0_r_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg0_0_l_0_r_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_0_l_0_r_gep)\n %build_e_arg0_0_l_0_v = call ptr @sz_pair_new(ptr %build_e_arg0_0_l_0_l_s, ptr %build_e_arg0_0_l_0_r_s)\n call void @sz_release(ptr %build_e_arg0_0_l_0_l_s)\n call void @sz_release(ptr %build_e_arg0_0_l_0_r_s)\n %build_e_arg0_0_r_gep = getelementptr inbounds [2 x i8], ptr @.str2, i64 0, i64 0\n %build_e_arg0_0_r_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_0_r_gep)\n %build_e_arg0_0_v = call ptr @sz_pair_new(ptr %build_e_arg0_0_l_0_v, ptr %build_e_arg0_0_r_s)\n call void @sz_release(ptr %build_e_arg0_0_l_0_v)\n call void @sz_release(ptr %build_e_arg0_0_r_s)\n %build_e_v = call ptr @sz_user_Main_take(ptr %build_e_arg0_0_v)\n call void @sz_release(ptr %build_e_arg0_0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"y\\00\", align 1\n@.str2 = private unnamed_addr constant [2 x i8] c\"z\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:6:3\\00\", align 1\n@.str4 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n@.str5 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:1:5@86#0\\00\", align 1\n\ndefine internal ptr @sz_user_Main_take(ptr %p) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n br label %body_try_0_0\nbody_try_0_0:\n %body_p0_0_tl1 = call ptr @sz_pair_left(ptr %p)\n %body_p0_0_tr1 = call ptr @sz_pair_right(ptr %p)\n br label %body_p0_0_tn1\nbody_p0_0_tn1:\n %body_p0_0_tl1_tl1 = call ptr @sz_pair_left(ptr %body_p0_0_tl1)\n %body_p0_0_tl1_tr1 = call ptr @sz_pair_right(ptr %body_p0_0_tl1)\n br label %body_p0_0_tl1_tn1\nbody_p0_0_tl1_tn1:\n br label %body_ok_0_0\nbody_default_0:\n br label %body_merge_0\nbody_ok_0_0:\n %body_cv0g0 = getelementptr inbounds [20 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_coverage_hit(ptr %body_cv0g0)\n %body_a0_0_arg1_v = call ptr @sz_string_concat(ptr %body_p0_0_tl1_tr1, ptr %body_p0_0_tr1)\n %body_a0_0_v = call ptr @sz_string_concat(ptr %body_p0_0_tl1_tl1, ptr %body_a0_0_arg1_v)\n call void @sz_release(ptr %body_a0_0_arg1_v)\n br label %body_aj_0_0\nbody_aj_0_0:\n br label %body_merge_0\nbody_merge_0:\n %body_phi = phi ptr [ %body_a0_0_v, %body_aj_0_0 ], [ null, %body_default_0 ]\n call void @sz_panic_pop_src()\n ret ptr %body_phi\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_0_l_0_l_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_0_l_0_l_s = call ptr @sz_string_lit(ptr %build_e_arg0_0_l_0_l_gep)\n %build_e_arg0_0_l_0_r_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg0_0_l_0_r_s = call ptr @sz_string_lit(ptr %build_e_arg0_0_l_0_r_gep)\n %build_e_arg0_0_l_0_v = call ptr @sz_pair_new(ptr %build_e_arg0_0_l_0_l_s, ptr %build_e_arg0_0_l_0_r_s)\n call void @sz_release(ptr %build_e_arg0_0_l_0_l_s)\n call void @sz_release(ptr %build_e_arg0_0_l_0_r_s)\n %build_e_arg0_0_r_gep = getelementptr inbounds [2 x i8], ptr @.str2, i64 0, i64 0\n %build_e_arg0_0_r_s = call ptr @sz_string_lit(ptr %build_e_arg0_0_r_gep)\n %build_e_arg0_0_v = call ptr @sz_pair_new(ptr %build_e_arg0_0_l_0_v, ptr %build_e_arg0_0_r_s)\n call void @sz_release(ptr %build_e_arg0_0_l_0_v)\n call void @sz_release(ptr %build_e_arg0_0_r_s)\n %build_e_v = call ptr @sz_user_Main_take(ptr %build_e_arg0_0_v)\n call void @sz_release(ptr %build_e_arg0_0_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcIfIo(): String = "@main def main: IO[Unit] =\n if (true) IO.println(\"a\") else IO.println(\"b\")\n" @@ -506,28 +506,28 @@ def srcIfFm(): String = "@main def main: IO[Unit] =\n if (true) IO.println(\"a\").flatMap(_ => IO.println(\"c\")) else IO.println(\"b\").flatMap(_ => IO.println(\"d\"))\n" def wantIfFm(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"c\\00\", align 1\n@.str2 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str3 = private unnamed_addr constant [2 x i8] c\"d\\00\", align 1\n@.str4 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n@.str5 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:2:3@39#t\\00\", align 1\n@.str6 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:2:3@90#e\\00\", align 1\n\ndefine internal ptr @sz_cont_0(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %c0_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %c0_ss = call ptr @sz_string_from_cstr(ptr %c0_gep)\n %c0_io = call ptr @sz_io_println(ptr %c0_ss)\n call void @sz_release(ptr %c0_ss)\n call void @sz_panic_pop_src()\n ret ptr %c0_io\n}\n\ndefine internal ptr @sz_cont_1(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %c1_gep = getelementptr inbounds [2 x i8], ptr @.str3, i64 0, i64 0\n %c1_ss = call ptr @sz_string_from_cstr(ptr %c1_gep)\n %c1_io = call ptr @sz_io_println(ptr %c1_ss)\n call void @sz_release(ptr %c1_ss)\n call void @sz_panic_pop_src()\n ret ptr %c1_io\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_cmp = icmp ne i64 1, 0\n br i1 %build_cmp, label %build_then_0, label %build_else_0\nbuild_then_0:\n %build_cvtg0 = getelementptr inbounds [20 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_cvtg0)\n %build_t0_in_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_t0_in_ss = call ptr @sz_string_from_cstr(ptr %build_t0_in_gep)\n %build_t0_in_io = call ptr @sz_io_println(ptr %build_t0_in_ss)\n call void @sz_release(ptr %build_t0_in_ss)\n %build_t0_fm = call ptr @sz_io_flatmap(ptr %build_t0_in_io, ptr @sz_cont_0, ptr null)\n call void @sz_release(ptr %build_t0_in_io)\n call void @sz_release(ptr null)\n br label %build_tj_0\nbuild_tj_0:\n br label %build_merge_0\nbuild_else_0:\n %build_cveg0 = getelementptr inbounds [20 x i8], ptr @.str6, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_cveg0)\n %build_e0_in_gep = getelementptr inbounds [2 x i8], ptr @.str2, i64 0, i64 0\n %build_e0_in_ss = call ptr @sz_string_from_cstr(ptr %build_e0_in_gep)\n %build_e0_in_io = call ptr @sz_io_println(ptr %build_e0_in_ss)\n call void @sz_release(ptr %build_e0_in_ss)\n %build_e0_fm = call ptr @sz_io_flatmap(ptr %build_e0_in_io, ptr @sz_cont_1, ptr null)\n call void @sz_release(ptr %build_e0_in_io)\n call void @sz_release(ptr null)\n br label %build_ej_0\nbuild_ej_0:\n br label %build_merge_0\nbuild_merge_0:\n %build_phi = phi ptr [ %build_t0_fm, %build_tj_0 ], [ %build_e0_fm, %build_ej_0 ]\n %rc = call i32 @sz_runtime_main_args(ptr %build_phi, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"c\\00\", align 1\n@.str2 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str3 = private unnamed_addr constant [2 x i8] c\"d\\00\", align 1\n@.str4 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n@.str5 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:2:3@39#t\\00\", align 1\n@.str6 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:2:3@90#e\\00\", align 1\n\ndefine internal ptr @sz_cont_0(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %c0_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %c0_ss = call ptr @sz_string_lit(ptr %c0_gep)\n %c0_io = call ptr @sz_io_println(ptr %c0_ss)\n call void @sz_release(ptr %c0_ss)\n call void @sz_panic_pop_src()\n ret ptr %c0_io\n}\n\ndefine internal ptr @sz_cont_1(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %c1_gep = getelementptr inbounds [2 x i8], ptr @.str3, i64 0, i64 0\n %c1_ss = call ptr @sz_string_lit(ptr %c1_gep)\n %c1_io = call ptr @sz_io_println(ptr %c1_ss)\n call void @sz_release(ptr %c1_ss)\n call void @sz_panic_pop_src()\n ret ptr %c1_io\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_cmp = icmp ne i64 1, 0\n br i1 %build_cmp, label %build_then_0, label %build_else_0\nbuild_then_0:\n %build_cvtg0 = getelementptr inbounds [20 x i8], ptr @.str5, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_cvtg0)\n %build_t0_in_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_t0_in_ss = call ptr @sz_string_lit(ptr %build_t0_in_gep)\n %build_t0_in_io = call ptr @sz_io_println(ptr %build_t0_in_ss)\n call void @sz_release(ptr %build_t0_in_ss)\n %build_t0_fm = call ptr @sz_io_flatmap(ptr %build_t0_in_io, ptr @sz_cont_0, ptr null)\n call void @sz_release(ptr %build_t0_in_io)\n call void @sz_release(ptr null)\n br label %build_tj_0\nbuild_tj_0:\n br label %build_merge_0\nbuild_else_0:\n %build_cveg0 = getelementptr inbounds [20 x i8], ptr @.str6, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_cveg0)\n %build_e0_in_gep = getelementptr inbounds [2 x i8], ptr @.str2, i64 0, i64 0\n %build_e0_in_ss = call ptr @sz_string_lit(ptr %build_e0_in_gep)\n %build_e0_in_io = call ptr @sz_io_println(ptr %build_e0_in_ss)\n call void @sz_release(ptr %build_e0_in_ss)\n %build_e0_fm = call ptr @sz_io_flatmap(ptr %build_e0_in_io, ptr @sz_cont_1, ptr null)\n call void @sz_release(ptr %build_e0_in_io)\n call void @sz_release(ptr null)\n br label %build_ej_0\nbuild_ej_0:\n br label %build_merge_0\nbuild_merge_0:\n %build_phi = phi ptr [ %build_t0_fm, %build_tj_0 ], [ %build_e0_fm, %build_ej_0 ]\n %rc = call i32 @sz_runtime_main_args(ptr %build_phi, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def wantIfIo(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n@.str3 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:2:3@39#t\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:2:3@60#e\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_cmp = icmp ne i64 1, 0\n br i1 %build_cmp, label %build_then_0, label %build_else_0\nbuild_then_0:\n %build_cvtg0 = getelementptr inbounds [20 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_cvtg0)\n %build_t0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_t0_ss = call ptr @sz_string_from_cstr(ptr %build_t0_gep)\n %build_t0_io = call ptr @sz_io_println(ptr %build_t0_ss)\n call void @sz_release(ptr %build_t0_ss)\n br label %build_tj_0\nbuild_tj_0:\n br label %build_merge_0\nbuild_else_0:\n %build_cveg0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_cveg0)\n %build_e0_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e0_ss = call ptr @sz_string_from_cstr(ptr %build_e0_gep)\n %build_e0_io = call ptr @sz_io_println(ptr %build_e0_ss)\n call void @sz_release(ptr %build_e0_ss)\n br label %build_ej_0\nbuild_ej_0:\n br label %build_merge_0\nbuild_merge_0:\n %build_phi = phi ptr [ %build_t0_io, %build_tj_0 ], [ %build_e0_io, %build_ej_0 ]\n %rc = call i32 @sz_runtime_main_args(ptr %build_phi, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n@.str3 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:2:3@39#t\\00\", align 1\n@.str4 = private unnamed_addr constant [20 x i8] c\"Main.scuzz:2:3@60#e\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_cmp = icmp ne i64 1, 0\n br i1 %build_cmp, label %build_then_0, label %build_else_0\nbuild_then_0:\n %build_cvtg0 = getelementptr inbounds [20 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_cvtg0)\n %build_t0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_t0_ss = call ptr @sz_string_lit(ptr %build_t0_gep)\n %build_t0_io = call ptr @sz_io_println(ptr %build_t0_ss)\n call void @sz_release(ptr %build_t0_ss)\n br label %build_tj_0\nbuild_tj_0:\n br label %build_merge_0\nbuild_else_0:\n %build_cveg0 = getelementptr inbounds [20 x i8], ptr @.str4, i64 0, i64 0\n call void @sz_coverage_hit(ptr %build_cveg0)\n %build_e0_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e0_ss = call ptr @sz_string_lit(ptr %build_e0_gep)\n %build_e0_io = call ptr @sz_io_println(ptr %build_e0_ss)\n call void @sz_release(ptr %build_e0_ss)\n br label %build_ej_0\nbuild_ej_0:\n br label %build_merge_0\nbuild_merge_0:\n %build_phi = phi ptr [ %build_t0_io, %build_tj_0 ], [ %build_e0_io, %build_ej_0 ]\n %rc = call i32 @sz_runtime_main_args(ptr %build_phi, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcFs(): String = "@main def main: IO[Unit] =\n Fs.read(\"x\").flatMap(s => IO.println(s))\n" def wantFs(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine internal ptr @sz_cont_0(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %s = bitcast ptr %.closure.value to ptr\n %c0_io = call ptr @sz_io_println(ptr %s)\n call void @sz_release(ptr %.closure.value)\n call void @sz_panic_pop_src()\n ret ptr %c0_io\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_in_arg0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_in_arg0_s = call ptr @sz_string_from_cstr(ptr %build_in_arg0_gep)\n %build_in_v = call ptr @sz_fs_read(ptr %build_in_arg0_s)\n call void @sz_release(ptr %build_in_arg0_s)\n %build_fm = call ptr @sz_io_flatmap(ptr %build_in_v, ptr @sz_cont_0, ptr null)\n call void @sz_release(ptr %build_in_v)\n call void @sz_release(ptr null)\n %rc = call i32 @sz_runtime_main_args(ptr %build_fm, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine internal ptr @sz_cont_0(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %s = bitcast ptr %.closure.value to ptr\n %c0_io = call ptr @sz_io_println(ptr %s)\n call void @sz_release(ptr %.closure.value)\n call void @sz_panic_pop_src()\n ret ptr %c0_io\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_in_arg0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_in_arg0_s = call ptr @sz_string_lit(ptr %build_in_arg0_gep)\n %build_in_v = call ptr @sz_fs_read(ptr %build_in_arg0_s)\n call void @sz_release(ptr %build_in_arg0_s)\n %build_fm = call ptr @sz_io_flatmap(ptr %build_in_v, ptr @sz_cont_0, ptr null)\n call void @sz_release(ptr %build_in_v)\n call void @sz_release(ptr null)\n %rc = call i32 @sz_runtime_main_args(ptr %build_fm, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcListFs(): String = "@main def main: IO[Unit] =\n Fs.list(\"x\").flatMap(s => IO.println(Str.fromInt(List.len(s))))\n" def wantListFs(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine internal ptr @sz_cont_0(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %s = bitcast ptr %.closure.value to ptr\n %c0_e_arg0_v = call i64 @sz_list_len(ptr %s)\n %c0_e_v = call ptr @sz_string_from_int(i64 %c0_e_arg0_v)\n %c0_io = call ptr @sz_io_println(ptr %c0_e_v)\n call void @sz_release(ptr %c0_e_v)\n call void @sz_release(ptr %.closure.value)\n call void @sz_panic_pop_src()\n ret ptr %c0_io\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_in_arg0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_in_arg0_s = call ptr @sz_string_from_cstr(ptr %build_in_arg0_gep)\n %build_in_v = call ptr @sz_fs_list(ptr %build_in_arg0_s)\n call void @sz_release(ptr %build_in_arg0_s)\n %build_fm = call ptr @sz_io_flatmap(ptr %build_in_v, ptr @sz_cont_0, ptr null)\n call void @sz_release(ptr %build_in_v)\n call void @sz_release(ptr null)\n %rc = call i32 @sz_runtime_main_args(ptr %build_fm, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"x\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine internal ptr @sz_cont_0(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %s = bitcast ptr %.closure.value to ptr\n %c0_e_arg0_v = call i64 @sz_list_len(ptr %s)\n %c0_e_v = call ptr @sz_string_from_int(i64 %c0_e_arg0_v)\n %c0_io = call ptr @sz_io_println(ptr %c0_e_v)\n call void @sz_release(ptr %c0_e_v)\n call void @sz_release(ptr %.closure.value)\n call void @sz_panic_pop_src()\n ret ptr %c0_io\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_in_arg0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_in_arg0_s = call ptr @sz_string_lit(ptr %build_in_arg0_gep)\n %build_in_v = call ptr @sz_fs_list(ptr %build_in_arg0_s)\n call void @sz_release(ptr %build_in_arg0_s)\n %build_fm = call ptr @sz_io_flatmap(ptr %build_in_v, ptr @sz_cont_0, ptr null)\n call void @sz_release(ptr %build_in_v)\n call void @sz_release(ptr null)\n %rc = call i32 @sz_runtime_main_args(ptr %build_fm, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcPure(): String = "@main def main: IO[Unit] =\n IO.pure(\"hi\").flatMap(s => IO.println(s))\n" def wantPure(): String = - "@.str0 = private unnamed_addr constant [3 x i8] c\"hi\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine internal ptr @sz_cont_0(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %s = bitcast ptr %.closure.value to ptr\n %c0_io = call ptr @sz_io_println(ptr %s)\n call void @sz_release(ptr %.closure.value)\n call void @sz_panic_pop_src()\n ret ptr %c0_io\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_in_p_gep = getelementptr inbounds [3 x i8], ptr @.str0, i64 0, i64 0\n %build_in_p_s = call ptr @sz_string_from_cstr(ptr %build_in_p_gep)\n %build_in_io = call ptr @sz_io_pure(ptr %build_in_p_s)\n call void @sz_release(ptr %build_in_p_s)\n %build_fm = call ptr @sz_io_flatmap(ptr %build_in_io, ptr @sz_cont_0, ptr null)\n call void @sz_release(ptr %build_in_io)\n call void @sz_release(ptr null)\n %rc = call i32 @sz_runtime_main_args(ptr %build_fm, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [3 x i8] c\"hi\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine internal ptr @sz_cont_0(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %s = bitcast ptr %.closure.value to ptr\n %c0_io = call ptr @sz_io_println(ptr %s)\n call void @sz_release(ptr %.closure.value)\n call void @sz_panic_pop_src()\n ret ptr %c0_io\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_in_p_gep = getelementptr inbounds [3 x i8], ptr @.str0, i64 0, i64 0\n %build_in_p_s = call ptr @sz_string_lit(ptr %build_in_p_gep)\n %build_in_io = call ptr @sz_io_pure(ptr %build_in_p_s)\n call void @sz_release(ptr %build_in_p_s)\n %build_fm = call ptr @sz_io_flatmap(ptr %build_in_io, ptr @sz_cont_0, ptr null)\n call void @sz_release(ptr %build_in_io)\n call void @sz_release(ptr null)\n %rc = call i32 @sz_runtime_main_args(ptr %build_fm, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def okHi(): Bool = internInterpLit() && rewriteInterpLit() && internOrder() && irDiff(srcHi(), wantHi()) && irIdem(srcHi()) && irDiff(srcSum(), wantSum()) && irIdem(srcSum()) && irDiff(srcAdd(), wantAdd()) && irIdem(srcAdd()) && irDiff(srcLib(), wantLib()) && irIdem(srcLib()) && irDiff(srcHello(), wantHello()) && irIdem(srcHello()) && Check.check(srcHello()) == "[]" && Str.startsWith(Emit.emitFull(srcHello()), "; Scuzz Lang generated LLVM IR") && Str.startsWith(Emit.emitFull(srcHello()), Emit.preamble()) && Emit.emitFull(srcHello()) == Str.concat(Emit.preamble(), wantHello()) @@ -566,7 +566,7 @@ def srcJoin(): String = "@main def main: IO[Unit] =\n IO.println(List.join([\"a\", \"b\"], \",\"))\n" def wantJoin(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [2 x i8] c\",\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_0 = call ptr @sz_list_nil()\n %build_e_arg0_e1_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg0_e1_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_e1_gep)\n %build_e_arg0_2 = call ptr @sz_list_cons(ptr %build_e_arg0_e1_s, ptr %build_e_arg0_0)\n call void @sz_release(ptr %build_e_arg0_0)\n call void @sz_release(ptr %build_e_arg0_e1_s)\n %build_e_arg0_e0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_e0_s = call ptr @sz_string_from_cstr(ptr %build_e_arg0_e0_gep)\n %build_e_arg0_1 = call ptr @sz_list_cons(ptr %build_e_arg0_e0_s, ptr %build_e_arg0_2)\n call void @sz_release(ptr %build_e_arg0_2)\n call void @sz_release(ptr %build_e_arg0_e0_s)\n %build_e_arg1_gep = getelementptr inbounds [2 x i8], ptr @.str2, i64 0, i64 0\n %build_e_arg1_s = call ptr @sz_string_from_cstr(ptr %build_e_arg1_gep)\n %build_e_v = call ptr @sz_list_join(ptr %build_e_arg0_1, ptr %build_e_arg1_s)\n call void @sz_release(ptr %build_e_arg0_1)\n call void @sz_release(ptr %build_e_arg1_s)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [2 x i8] c\",\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_0 = call ptr @sz_list_nil()\n %build_e_arg0_e1_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_arg0_e1_s = call ptr @sz_string_lit(ptr %build_e_arg0_e1_gep)\n %build_e_arg0_2 = call ptr @sz_list_cons(ptr %build_e_arg0_e1_s, ptr %build_e_arg0_0)\n call void @sz_release(ptr %build_e_arg0_0)\n call void @sz_release(ptr %build_e_arg0_e1_s)\n %build_e_arg0_e0_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_e_arg0_e0_s = call ptr @sz_string_lit(ptr %build_e_arg0_e0_gep)\n %build_e_arg0_1 = call ptr @sz_list_cons(ptr %build_e_arg0_e0_s, ptr %build_e_arg0_2)\n call void @sz_release(ptr %build_e_arg0_2)\n call void @sz_release(ptr %build_e_arg0_e0_s)\n %build_e_arg1_gep = getelementptr inbounds [2 x i8], ptr @.str2, i64 0, i64 0\n %build_e_arg1_s = call ptr @sz_string_lit(ptr %build_e_arg1_gep)\n %build_e_v = call ptr @sz_list_join(ptr %build_e_arg0_1, ptr %build_e_arg1_s)\n call void @sz_release(ptr %build_e_arg0_1)\n call void @sz_release(ptr %build_e_arg1_s)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcApply(): String = """def applyInt(f: Int => Int, n: Int): Int = @@ -586,7 +586,7 @@ def srcInterp(): String = "@main def main: IO[Unit] =\n IO.println(s\"n:${Str.fromInt(3)}\")\n" def wantInterp(): String = - "@.str0 = private unnamed_addr constant [3 x i8] c\"n:\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_l0_gep = getelementptr inbounds [3 x i8], ptr @.str0, i64 0, i64 0\n %build_e_l0_s = call ptr @sz_string_from_cstr(ptr %build_e_l0_gep)\n %build_e_e1_v = call ptr @sz_string_from_int(i64 3)\n %build_e_c1_v = call ptr @sz_string_concat(ptr %build_e_l0_s, ptr %build_e_e1_v)\n call void @sz_release(ptr %build_e_l0_s)\n call void @sz_release(ptr %build_e_e1_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_c1_v)\n call void @sz_release(ptr %build_e_c1_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [3 x i8] c\"n:\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_l0_gep = getelementptr inbounds [3 x i8], ptr @.str0, i64 0, i64 0\n %build_e_l0_s = call ptr @sz_string_lit(ptr %build_e_l0_gep)\n %build_e_e1_v = call ptr @sz_string_from_int(i64 3)\n %build_e_c1_v = call ptr @sz_string_concat(ptr %build_e_l0_s, ptr %build_e_e1_v)\n call void @sz_release(ptr %build_e_l0_s)\n call void @sz_release(ptr %build_e_e1_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_c1_v)\n call void @sz_release(ptr %build_e_c1_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcForEq(): String = """@main def main: IO[Unit] = @@ -603,13 +603,13 @@ def srcFor(): String = "@main def main: IO[Unit] =\n for {\n _ <- IO.println(\"a\")\n _ <- IO.println(\"b\")\n } yield ()\n" def wantFor(): String = - "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine internal ptr @sz_cont_0(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %c0_io = call ptr @sz_io_pure(ptr null)\n call void @sz_release(ptr null)\n call void @sz_panic_pop_src()\n ret ptr %c0_io\n}\n\ndefine internal ptr @sz_cont_1(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %c1_in_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %c1_in_ss = call ptr @sz_string_from_cstr(ptr %c1_in_gep)\n %c1_in_io = call ptr @sz_io_println(ptr %c1_in_ss)\n call void @sz_release(ptr %c1_in_ss)\n %c1_fm = call ptr @sz_io_flatmap(ptr %c1_in_io, ptr @sz_cont_0, ptr null)\n call void @sz_release(ptr %c1_in_io)\n call void @sz_release(ptr null)\n call void @sz_panic_pop_src()\n ret ptr %c1_fm\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_in_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_in_ss = call ptr @sz_string_from_cstr(ptr %build_in_gep)\n %build_in_io = call ptr @sz_io_println(ptr %build_in_ss)\n call void @sz_release(ptr %build_in_ss)\n %build_fm = call ptr @sz_io_flatmap(ptr %build_in_io, ptr @sz_cont_1, ptr null)\n call void @sz_release(ptr %build_in_io)\n call void @sz_release(ptr null)\n %rc = call i32 @sz_runtime_main_args(ptr %build_fm, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str2 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine internal ptr @sz_cont_0(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %c0_io = call ptr @sz_io_pure(ptr null)\n call void @sz_release(ptr null)\n call void @sz_panic_pop_src()\n ret ptr %c0_io\n}\n\ndefine internal ptr @sz_cont_1(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %c1_in_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %c1_in_ss = call ptr @sz_string_lit(ptr %c1_in_gep)\n %c1_in_io = call ptr @sz_io_println(ptr %c1_in_ss)\n call void @sz_release(ptr %c1_in_ss)\n %c1_fm = call ptr @sz_io_flatmap(ptr %c1_in_io, ptr @sz_cont_0, ptr null)\n call void @sz_release(ptr %c1_in_io)\n call void @sz_release(ptr null)\n call void @sz_panic_pop_src()\n ret ptr %c1_fm\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str2, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_in_gep = getelementptr inbounds [2 x i8], ptr @.str0, i64 0, i64 0\n %build_in_ss = call ptr @sz_string_lit(ptr %build_in_gep)\n %build_in_io = call ptr @sz_io_println(ptr %build_in_ss)\n call void @sz_release(ptr %build_in_ss)\n %build_fm = call ptr @sz_io_flatmap(ptr %build_in_io, ptr @sz_cont_1, ptr null)\n call void @sz_release(ptr %build_in_io)\n call void @sz_release(ptr null)\n %rc = call i32 @sz_runtime_main_args(ptr %build_fm, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcInterpQ(): String = "@main def main: IO[Unit] =\n IO.println(s\"x:${Str.concat(\"a\", \"b\")}\")\n" def wantInterpQ(): String = - "@.str0 = private unnamed_addr constant [3 x i8] c\"x:\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str2 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_l0_gep = getelementptr inbounds [3 x i8], ptr @.str0, i64 0, i64 0\n %build_e_l0_s = call ptr @sz_string_from_cstr(ptr %build_e_l0_gep)\n %build_e_e1_arg0_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_e1_arg0_s = call ptr @sz_string_from_cstr(ptr %build_e_e1_arg0_gep)\n %build_e_e1_arg1_gep = getelementptr inbounds [2 x i8], ptr @.str2, i64 0, i64 0\n %build_e_e1_arg1_s = call ptr @sz_string_from_cstr(ptr %build_e_e1_arg1_gep)\n %build_e_e1_v = call ptr @sz_string_concat(ptr %build_e_e1_arg0_s, ptr %build_e_e1_arg1_s)\n call void @sz_release(ptr %build_e_e1_arg0_s)\n call void @sz_release(ptr %build_e_e1_arg1_s)\n %build_e_c1_v = call ptr @sz_string_concat(ptr %build_e_l0_s, ptr %build_e_e1_v)\n call void @sz_release(ptr %build_e_l0_s)\n call void @sz_release(ptr %build_e_e1_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_c1_v)\n call void @sz_release(ptr %build_e_c1_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" + "@.str0 = private unnamed_addr constant [3 x i8] c\"x:\\00\", align 1\n@.str1 = private unnamed_addr constant [2 x i8] c\"a\\00\", align 1\n@.str2 = private unnamed_addr constant [2 x i8] c\"b\\00\", align 1\n@.str3 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:2:3\\00\", align 1\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str3, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_l0_gep = getelementptr inbounds [3 x i8], ptr @.str0, i64 0, i64 0\n %build_e_l0_s = call ptr @sz_string_lit(ptr %build_e_l0_gep)\n %build_e_e1_arg0_gep = getelementptr inbounds [2 x i8], ptr @.str1, i64 0, i64 0\n %build_e_e1_arg0_s = call ptr @sz_string_lit(ptr %build_e_e1_arg0_gep)\n %build_e_e1_arg1_gep = getelementptr inbounds [2 x i8], ptr @.str2, i64 0, i64 0\n %build_e_e1_arg1_s = call ptr @sz_string_lit(ptr %build_e_e1_arg1_gep)\n %build_e_e1_v = call ptr @sz_string_concat(ptr %build_e_e1_arg0_s, ptr %build_e_e1_arg1_s)\n call void @sz_release(ptr %build_e_e1_arg0_s)\n call void @sz_release(ptr %build_e_e1_arg1_s)\n %build_e_c1_v = call ptr @sz_string_concat(ptr %build_e_l0_s, ptr %build_e_e1_v)\n call void @sz_release(ptr %build_e_l0_s)\n call void @sz_release(ptr %build_e_e1_v)\n %build_io = call ptr @sz_io_println(ptr %build_e_c1_v)\n call void @sz_release(ptr %build_e_c1_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def srcLam(): String = """def plusOne(): Int => Int = diff --git a/examples/compiler/src/Emit.scuzz b/examples/compiler/src/Emit.scuzz index 2281d717..d4792a77 100644 --- a/examples/compiler/src/Emit.scuzz +++ b/examples/compiler/src/Emit.scuzz @@ -607,7 +607,7 @@ def emitStrAs(s: String, prefix: String, strs: List[String], stem: String): Slot emitStr2(prefix, strIndex(strs, s, 0), strArr(s), stem) def emitStr2(prefix: String, idx: Int, len: Int, stem: String): Slot = - Slot(join(line(Str.concat(tmp(prefix, "gep"), Str.concat("getelementptr inbounds [", Str.concat(Str.fromInt(len), Str.concat(" x i8], ptr @.str", Str.concat(Str.fromInt(idx), ", i64 0, i64 0")))))), line(Str.concat(tmp(prefix, stem), Str.concat("call ptr @sz_string_from_cstr(ptr ", Str.concat(pct(prefix, "gep"), ")"))))), pct(prefix, stem), true) + Slot(join(line(Str.concat(tmp(prefix, "gep"), Str.concat("getelementptr inbounds [", Str.concat(Str.fromInt(len), Str.concat(" x i8], ptr @.str", Str.concat(Str.fromInt(idx), ", i64 0, i64 0")))))), line(Str.concat(tmp(prefix, stem), Str.concat("call ptr @sz_string_lit(ptr ", Str.concat(pct(prefix, "gep"), ")"))))), pct(prefix, stem), true) def emitPrint(inner: Expr, prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = emitPrint2(emitSzString(inner, prefix, strs, defs, ens, ps, loc), prefix) @@ -3974,7 +3974,7 @@ def tupleStrTest(comp: String, pre: String, sid: Int, side: String, strs: List[S join(tupleStrCompare(unquote(comp), pre, side, tupleSideVal(pre, sid, side), strs), join(line(Str.concat("br i1 ", Str.concat(pct(pre, Str.concat("tseq", side)), Str.concat(", label %", Str.concat(tupleContStem(pre, side), Str.concat(", label %", fail)))))), join(lab(tupleContStem(pre, side)), cont))) def tupleStrCompare(s: String, pre: String, side: String, val: String, strs: List[String]): String = - join(line(Str.concat(tmp(pre, Str.concat("tsgep", side)), Str.concat("getelementptr inbounds [", Str.concat(Str.fromInt(strArr(s)), Str.concat(" x i8], ptr @.str", Str.concat(Str.fromInt(strIndex(strs, s, 0)), ", i64 0, i64 0")))))), join(line(Str.concat(tmp(pre, Str.concat("tslit", side)), Str.concat("call ptr @sz_string_from_cstr(ptr ", Str.concat(pct(pre, Str.concat("tsgep", side)), ")")))), join(line(Str.concat(tmp(pre, Str.concat("tseqi", side)), Str.concat("call i32 @sz_string_eq(ptr ", Str.concat(val, Str.concat(", ptr ", Str.concat(pct(pre, Str.concat("tslit", side)), ")")))))), join(relPtr(pct(pre, Str.concat("tslit", side))), line(Str.concat(tmp(pre, Str.concat("tseq", side)), Str.concat("icmp ne i32 ", Str.concat(pct(pre, Str.concat("tseqi", side)), ", 0")))))))) + join(line(Str.concat(tmp(pre, Str.concat("tsgep", side)), Str.concat("getelementptr inbounds [", Str.concat(Str.fromInt(strArr(s)), Str.concat(" x i8], ptr @.str", Str.concat(Str.fromInt(strIndex(strs, s, 0)), ", i64 0, i64 0")))))), join(line(Str.concat(tmp(pre, Str.concat("tslit", side)), Str.concat("call ptr @sz_string_lit(ptr ", Str.concat(pct(pre, Str.concat("tsgep", side)), ")")))), join(line(Str.concat(tmp(pre, Str.concat("tseqi", side)), Str.concat("call i32 @sz_string_eq(ptr ", Str.concat(val, Str.concat(", ptr ", Str.concat(pct(pre, Str.concat("tslit", side)), ")")))))), join(relPtr(pct(pre, Str.concat("tslit", side))), line(Str.concat(tmp(pre, Str.concat("tseq", side)), Str.concat("icmp ne i32 ", Str.concat(pct(pre, Str.concat("tseqi", side)), ", 0")))))))) def headTy(tys: List[String]): String = if (List.isEmpty(tys)) "" else List.at(tys, 0) @@ -4135,7 +4135,7 @@ def strGep(s: String, prefix: String, i: Int, tag: String, idx: Int): String = line(Str.concat(tmp(ppre(prefix, i), Str.concat("sgep", Str.concat(sid(i), tag))), Str.concat("getelementptr inbounds [", Str.concat(Str.fromInt(strArr(s)), Str.concat(" x i8], ptr @.str", Str.concat(Str.fromInt(idx), ", i64 0, i64 0")))))) def strLit(prefix: String, i: Int, tag: String): String = - line(Str.concat(tmp(ppre(prefix, i), Str.concat("slit", Str.concat(sid(i), tag))), Str.concat("call ptr @sz_string_from_cstr(ptr ", Str.concat(pct(ppre(prefix, i), Str.concat("sgep", Str.concat(sid(i), tag))), ")")))) + line(Str.concat(tmp(ppre(prefix, i), Str.concat("slit", Str.concat(sid(i), tag))), Str.concat("call ptr @sz_string_lit(ptr ", Str.concat(pct(ppre(prefix, i), Str.concat("sgep", Str.concat(sid(i), tag))), ")")))) def strEqi(prefix: String, i: Int, tag: String, scrut: String): String = line(Str.concat(tmp(ppre(prefix, i), Str.concat("seqi", Str.concat(sid(i), tag))), Str.concat("call i32 @sz_string_eq(ptr ", Str.concat(scrut, Str.concat(", ptr ", Str.concat(pct(ppre(prefix, i), Str.concat("slit", Str.concat(sid(i), tag))), ")")))))) @@ -5725,7 +5725,7 @@ def decodePanic(prefix: String, fail: Int): String = join(fromCstrLine(prefix, fail), join(panicLine(prefix, fail), line("unreachable"))) def fromCstrLine(prefix: String, fail: Int): String = - line(Str.concat(decPct(prefix, "ps", fail), Str.concat(" = call ptr @sz_string_from_cstr(ptr ", Str.concat(decPct(prefix, "g", fail), ")")))) + line(Str.concat(decPct(prefix, "ps", fail), Str.concat(" = call ptr @sz_string_lit(ptr ", Str.concat(decPct(prefix, "g", fail), ")")))) def panicLine(prefix: String, fail: Int): String = line(Str.concat("call void @sz_panic(ptr ", Str.concat(decPct(prefix, "ps", fail), ")"))) @@ -5758,7 +5758,7 @@ def drvGep(name: String, strs: List[String], i: Int): String = line(Str.concat("%drv", Str.concat(Str.fromInt(i), Str.concat("_gep = getelementptr inbounds [", Str.concat(Str.fromInt(strArr(name)), Str.concat(" x i8], ptr @.str", Str.concat(Str.fromInt(strIndex(strs, name, 0)), ", i64 0, i64 0"))))))) def drvSs(i: Int): String = - line(Str.concat("%drv", Str.concat(Str.fromInt(i), Str.concat("_ss = call ptr @sz_string_from_cstr(ptr %drv", Str.concat(Str.fromInt(i), "_gep)"))))) + line(Str.concat("%drv", Str.concat(Str.fromInt(i), Str.concat("_ss = call ptr @sz_string_lit(ptr %drv", Str.concat(Str.fromInt(i), "_gep)"))))) def drvCall(name: String, ps: List[Param], mod: String, i: Int): String = line(Str.concat("call void @sz_driver_register(ptr %drv", Str.concat(Str.fromInt(i), Str.concat("_ss, i64 ", Str.concat(Str.fromInt(List.len(ps)), Str.concat(", i64 ", Str.concat(Str.fromInt(packKind(ps)), Str.concat(", ptr ", Str.concat(drvSym(name, ps, mod, i), ")"))))))))) @@ -6395,7 +6395,7 @@ def declsX(): String = join(decl("ptr @sz_net_http_post(ptr, ptr, ptr)"), join(decl("ptr @sz_net_http_put(ptr, ptr, ptr)"), join(decl("ptr @sz_net_http_patch(ptr, ptr, ptr)"), join(decl("ptr @sz_net_http_delete(ptr, ptr)"), join(decl("ptr @sz_net_http_head(ptr, ptr)"), join(decl("ptr @sz_net_tcp_connect(ptr, i64)"), join(decl("ptr @sz_net_tcp_listen(i64)"), join(decl("ptr @sz_net_tcp_accept(ptr)"), join(decl("ptr @sz_net_tcp_read(ptr, i64)"), join(decl("ptr @sz_net_tcp_write(ptr, ptr)"), join(decl("ptr @sz_net_tcp_close(ptr)"), join(decl("ptr @sz_net_udp_bind(i64)"), join(decl("ptr @sz_net_udp_send(ptr, ptr, i64, ptr)"), join(decl("ptr @sz_net_udp_recv(ptr, i64)"), decl("ptr @sz_net_udp_close(ptr)"))))))))))))))) def declsA(): String = - join(decl("ptr @sz_string_from_cstr(ptr)"), join(decl("void @sz_retain(ptr)"), join(decl("void @sz_release(ptr)"), join(decl("ptr @sz_string_from_int(i64)"), join(decl("ptr @sz_string_from_bool(i64)"), join(decl("ptr @sz_string_concat(ptr, ptr)"), join(decl("ptr @sz_adt_new(i32, ptr)"), join(decl("i32 @sz_adt_tag(ptr)"), decl("ptr @sz_box_i64(i64)"))))))))) + join(decl("ptr @sz_string_from_cstr(ptr)"), join(decl("ptr @sz_string_lit(ptr)"), join(decl("void @sz_retain(ptr)"), join(decl("void @sz_release(ptr)"), join(decl("ptr @sz_string_from_int(i64)"), join(decl("ptr @sz_string_from_bool(i64)"), join(decl("ptr @sz_string_concat(ptr, ptr)"), join(decl("ptr @sz_adt_new(i32, ptr)"), join(decl("i32 @sz_adt_tag(ptr)"), decl("ptr @sz_box_i64(i64)")))))))))) def declsB(): String = join(decl("ptr @sz_list_nil()"), join(decl("ptr @sz_list_cons(ptr, ptr)"), join(decl("i64 @sz_list_len(ptr)"), join(decl("ptr @sz_list_concat(ptr, ptr)"), join(decl("ptr @sz_list_reverse(ptr)"), join(decl("ptr @sz_io_println(ptr)"), join(decl("ptr @sz_io_flatmap(ptr, ptr, ptr)"), decl("i32 @sz_runtime_main_args(ptr, i32, ptr)")))))))) diff --git a/scripts/ci.sh b/scripts/ci.sh index 9af769db..e7db0b7d 100755 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -373,6 +373,18 @@ extern SzString *sz_user_Main_tailValue(SzString *, int64_t); int main(void) { SzString *input = sz_string_from_cstr("payload"); size_t before, after; + { + /* Warm up: interned string literals pin on first evaluation. */ + SzString *warm = sz_user_Main_value(input); + sz_release(warm); + warm = sz_user_Main_recordValue(input); + sz_release(warm); + warm = sz_user_Main_tailValue(input, 1); + sz_release(warm); + assert(sz_user_Main_size(input) == 7); + assert(sz_user_Main_recordSize(input) == 7); + assert(sz_user_Main_tailSize(input, 1) == 7); + } sz_alloc_stats(&before, NULL); for (int i = 0; i < 1000; ++i) { assert(sz_user_Main_size(input) == 7); From 2bc7145af73ebecf5163498d87550ca7a5cd7939 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 16 Sep 2026 23:03:10 -0400 Subject: [PATCH 12/23] Allow nested constructor patterns so match can test inner tags. Check accepts nested ctor, tuple, cons, as, and [] patterns. Emit tests the inner structure and fails through to the next arm. --- docs/gaps.md | 2 +- docs/philosophy.md | 2 +- examples/codegen/src/Main.scuzz | 73 +++++++++++++++- examples/compiler/src/Check.scuzz | 32 +++---- examples/compiler/src/Emit.scuzz | 136 +++++++++++++++++++----------- examples/kernel/add.scuzz_verify | 3 + examples/kernel/src/Main.scuzz | 8 ++ examples/tyck/src/Main.scuzz | 47 +++++++---- 8 files changed, 216 insertions(+), 87 deletions(-) diff --git a/docs/gaps.md b/docs/gaps.md index 1bc0a74a..5f7dc319 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -37,7 +37,7 @@ Do not add library publishing, git or registry deps, or `scuzz add`. Path deps s Resolve these gaps when they prevent ordinary language use. -1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions and payloads seen through a lambda parameter over a collection. Applying an env-bound lambda with an unresolved parameter letter to a concrete argument fails check. An unannotated lambda in argument or def-body position binds its parameter from the expected function type, so a loose lambda can no longer escape through an expected function type. Param letters (`A`/`E`) still unify elsewhere in `Type.eq`. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Constructor patterns do not nest. A nested constructor, tuple, as, or cons pattern in a constructor field or tuple component fails check. A bare enum case, float literal, or list literal in those positions also fails check. Tuple components and direct constructor fields compare String, Int, and Bool literals. +1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions and payloads seen through a lambda parameter over a collection. Applying an env-bound lambda with an unresolved parameter letter to a concrete argument fails check. An unannotated lambda in argument or def-body position binds its parameter from the expected function type, so a loose lambda can no longer escape through an expected function type. Param letters (`A`/`E`) still unify elsewhere in `Type.eq`. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Tuple components and constructor fields compare String, Int, and Bool literals. Constructor, tuple, cons, as, and `[]` patterns nest in those positions. 2. **Compile-time performance** — `scuzz check examples/compiler` is 17 s. A cold `scuzz build examples/tyck` is 34 s. Emitted string literals intern to pinned allocations. Remaining cost: RC retain/release churn and `sz_list_concat` in string building. Coverage still parses a compiled graph that differs from live. diff --git a/docs/philosophy.md b/docs/philosophy.md index dff01a77..200bc05f 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -125,7 +125,7 @@ Locks (not an API catalog — run `scuzz docs language` and `scuzz docs kits`): - Optional `package`; top-level `def` / `private def` / `import`; `@main def …: IO[Unit]` - Payload enums + `record` sugar + thin traits/`impl` (static dispatch) + monomorphized generics - Record field lookup substitutes the receiver type arguments into the declared field type. The same rule applies inside callbacks. -- Constructor patterns compare direct String, Int, and Bool literals before an arm runs. Named fields use their declared positions. A failed literal comparison tries the next arm. +- Constructor patterns compare direct String, Int, and Bool literals before an arm runs. Named fields use their declared positions. A failed literal comparison tries the next arm. Constructor, tuple, cons, as, and `[]` patterns nest in constructor fields and tuple components. - Literal alternatives support chains of String, Int, or Bool values. Test each alternative before the arm guard. String contents can include the alternative separator. - Constructor alternatives check tags and direct literal fields. Alternatives with the same binding names and types share the selected payload values. Field positions can differ. `check` rejects alternatives whose binding names or types differ. Guards and IO assertions use these bindings. - Closure captures keep their declared types. Generated closure names cannot collide with source bindings. A local binding of `self` does not add an implicit parameter. diff --git a/examples/codegen/src/Main.scuzz b/examples/codegen/src/Main.scuzz index e4bf8a95..7761a1cb 100644 --- a/examples/codegen/src/Main.scuzz +++ b/examples/codegen/src/Main.scuzz @@ -542,7 +542,63 @@ def okList(): Bool = irDiff(srcList(), wantList()) && irIdem(srcList()) && irDiff(srcEmpty(), wantEmpty()) && irIdem(srcEmpty()) && irDiff(srcListCat(), wantListCat()) && irIdem(srcListCat()) && irDiff(srcListRev(), wantListRev()) && irIdem(srcListRev()) && Check.check(srcList()) == "[]" && Check.check(srcEmpty()) == "[]" && Check.check(srcListCat()) == "[]" && Check.check(srcListRev()) == "[]" def okOpt(): Bool = - irDiff(srcOpt(), wantOpt()) && irIdem(srcOpt()) && irDiff(srcHead(), wantHead()) && irIdem(srcHead()) && irDiff(srcEmptyQ(), wantEmptyQ()) && irIdem(srcEmptyQ()) && irDiff(srcTail(), wantTail()) && irIdem(srcTail()) && irDiff(srcTok(), wantTok()) && irIdem(srcTok()) && Check.check(srcOpt()) == "[]" && Check.check(srcHead()) == "[]" && Check.check(srcEmptyQ()) == "[]" && Check.check(srcTail()) == "[]" && Check.check(srcTok()) == "[]" && okHeadPair() + irDiff(srcOpt(), wantOpt()) && irIdem(srcOpt()) && irDiff(srcHead(), wantHead()) && irIdem(srcHead()) && irDiff(srcEmptyQ(), wantEmptyQ()) && irIdem(srcEmptyQ()) && irDiff(srcTail(), wantTail()) && irIdem(srcTail()) && irDiff(srcTok(), wantTok()) && irIdem(srcTok()) && Check.check(srcOpt()) == "[]" && Check.check(srcHead()) == "[]" && Check.check(srcEmptyQ()) == "[]" && Check.check(srcTail()) == "[]" && Check.check(srcTok()) == "[]" && okHeadPair() && okNestCtor() && okNestTupField() && okNestCons() + +def okNestCtor(): Bool = + Check.check(srcNestCtor()) == "[]" && hasNestCtor(Emit.emit(srcNestCtor())) && irIdem(srcNestCtor()) + +def okNestTupField(): Bool = + Check.check(srcNestTupField()) == "[]" && hasNestTupField(Emit.emit(srcNestTupField())) && irIdem(srcNestTupField()) + +def okNestCons(): Bool = + Check.check(srcNestCons()) == "[]" && hasNestCons(Emit.emit(srcNestCons())) && irIdem(srcNestCons()) + +def srcNestCtor(): String = + """enum Inner: + case Good(v: Int) +enum Outer: + case Wrap(a: Inner, b: Int) +def get(o: Outer): Int = + o match { + case Outer.Wrap(Inner.Good(n), _) => n + case _ => 0 + } +@main def main: IO[Unit] = + IO.println(Str.fromInt(get(Outer.Wrap(Inner.Good(7), 1)))) +""" + +def hasNestCtor(ir: String): Bool = + Str.contains(ir, "call i32 @sz_adt_tag(ptr %o)") && Str.contains(ir, "_n_p0_0_tg1") && Str.contains(ir, "call i64 @sz_unbox_i64") + +def srcNestTupField(): String = + """enum Box2: + case P(p: (Int, Int)) +def get(b: Box2): Int = + b match { + case Box2.P((n, m)) => n + m + case _ => 0 + } +@main def main: IO[Unit] = + IO.println(Str.fromInt(get(Box2.P((1, 2))))) +""" + +def hasNestTupField(ir: String): Bool = + Str.contains(ir, "call ptr @sz_adt_payload") && Str.contains(ir, "call ptr @sz_pair_left") && Str.contains(ir, "call ptr @sz_pair_right") && Str.contains(ir, "call i64 @sz_unbox_i64") + +def srcNestCons(): String = + """enum BoxL: + case L(xs: List[Int]) +def get(b: BoxL): Int = + b match { + case BoxL.L(x :: _) => x + case _ => 0 + } +@main def main: IO[Unit] = + IO.println(Str.fromInt(get(BoxL.L([7])))) +""" + +def hasNestCons(ir: String): Bool = + Str.contains(ir, "call ptr @sz_adt_payload") && Str.contains(ir, "call i32 @sz_list_is_empty") && Str.contains(ir, "call ptr @sz_list_head") && Str.contains(ir, "call i64 @sz_unbox_i64") def okHeadPair(): Bool = Check.check(srcHeadPair()) == "[]" && hasHeadPairBox(Emit.emit(srcHeadPair())) @@ -625,6 +681,18 @@ def wantLam(): String = def allOk(): Bool = okHi() && okIf() && okCmp() && okList() && okOpt() && okKw() && okPair() && okMod() && okPtr() && okFs() && irDiff(srcLam(), wantLam()) && irIdem(srcLam()) +def dumpAll(): String = + if (!okHi()) "okHi" else if (!okIf()) "okIf" else if (!okCmp()) "okCmp" else if (!okList()) "okList" else if (!okOpt()) dumpOpt() else if (!okKw()) "okKw" else if (!okPair()) "okPair" else if (!okMod()) "okMod" else if (!okPtr()) dumpPtr() else if (!okFs()) "okFs" else if (!irDiff(srcLam(), wantLam()) || !irIdem(srcLam())) "okLam" else "other" + +def dumpPtr(): String = + if (!irDiff(srcIfPtr(), wantIfPtr()) || !irIdem(srcIfPtr())) "srcIfPtr" else if (!irDiff(srcTCons(), wantTCons()) || !irIdem(srcTCons())) "srcTCons" else if (!irDiff(srcTupPtr(), wantTupPtr()) || !irIdem(srcTupPtr())) "srcTupPtr" else if (!irDiff(srcT3(), wantT3()) || !irIdem(srcT3())) "srcT3" else if (!irDiff(srcIfTup(), wantIfTup()) || !irIdem(srcIfTup())) "srcIfTup" else if (!irDiff(srcNestTup(), wantNestTup()) || !irIdem(srcNestTup())) "srcNestTup" else "okPtr-other" + +def dumpOpt(): String = + if (!irDiff(srcOpt(), wantOpt())) "srcOpt" else if (!irDiff(srcHead(), wantHead())) "srcHead" else if (!okHeadPair()) "okHeadPair" else if (!okNestCtor()) "okNestCtor" else if (!okNestTupField()) "okNestTupField" else if (!okNestCons()) "okNestCons" else "okOpt-other" + +@main def main: IO[Unit] = + IO.println(if (allOk()) "ir-ok" else dumpAll()) + def reloadCaptureOrder(): Bool = Emit.captureSchema([Param("a", "Int", "", ""), Param("b", "String", "", "")], []) != Emit.captureSchema([Param("b", "String", "", ""), Param("a", "Int", "", "")], []) @@ -633,6 +701,3 @@ def reloadCaptureType(): Bool = def reloadRecordLayout(): Bool = Emit.captureSchema([Param("s", "Signal[State]", "", "")], [En(true, "State", [], [EnCase("State", [Param("value", "Int", "", "")])])]) != Emit.captureSchema([Param("s", "Signal[State]", "", "")], [En(true, "State", [], [EnCase("State", [Param("value", "String", "", "")])])]) - -@main def main: IO[Unit] = - IO.println(if (allOk()) "ir-ok" else "ir-bad") diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index a147982e..515f65da 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -1122,10 +1122,10 @@ def nestPatErr(pat: String): String = if (asAt(pat, 0) >= 0) nestPatErr(trimName(Str.drop(pat, asAt(pat, 0) + 3))) else if (consAt(pat, 0) >= 0) nestConsHeadErr(trimName(Str.take(pat, consAt(pat, 0))), trimName(Str.drop(pat, consAt(pat, 0) + 4))) else if (isTuplePat(pat)) nestFieldsErr(splitComma(Str.slice(pat, 1, Str.len(pat) - 1)), true) else if (patBind(pat) == "") "" else nestFieldsErr(splitComma(patBind(pat)), false) def nestConsHeadErr(head: String, tail: String): String = - if (asAt(head, 0) >= 0) "nested patterns are not supported here" else if (nestSimple(head)) nestPatErr(tail) else "nested patterns are not supported here" + nestConsHeadErr2(nestPatErr(head), tail) -def nestSimple(s: String): Bool = - s == "" || s == "_" || altStrPat(s) || altIntPat(s) || altBoolPat(s) || isBareName(s) && dotAt(s, 0) < 0 +def nestConsHeadErr2(err: String, tail: String): String = + if (Str.len(err) > 0) err else nestPatErr(tail) def nestFieldsErr(fs: List[String], inTuple: Bool): String = if (List.isEmpty(fs)) "" else nestFieldsErr2(nestFieldErr(List.at(fs, 0), inTuple), List.tail(fs), inTuple) @@ -1139,14 +1139,11 @@ def nestFieldErr(f: String, inTuple: Bool): String = def nestFieldErrAt(i: Int, f: String, inTuple: Bool): String = nestCompErr(trimName(if (i < 0) f else Str.drop(f, i + 3)), inTuple) -def nestCompErr(c: String, inTuple: Bool): String = - if (asAt(c, 0) >= 0) "nested patterns are not supported here" else if (consAt(c, 0) >= 0) nestConsInErr(c, inTuple) else if (nestSimple(c)) "" else if (isTuplePat(c)) nestTupErr(c, inTuple) else "nested patterns are not supported here" +def nestCompErr(c: String, _inTuple: Bool): String = + if (nestPatOk(c)) nestPatErr(c) else "nested patterns are not supported here" -def nestTupErr(c: String, inTuple: Bool): String = - if (inTuple) nestFieldsErr(splitComma(Str.slice(c, 1, Str.len(c) - 1)), true) else "nested patterns are not supported here" - -def nestConsInErr(c: String, inTuple: Bool): String = - if (inTuple) "nested patterns are not supported here" else nestConsHeadErr(trimName(Str.take(c, consAt(c, 0))), trimName(Str.drop(c, consAt(c, 0) + 4))) +def nestPatOk(s: String): Bool = + s == "" || s == "_" || s == "[]" || altStrPat(s) || altIntPat(s) || altBoolPat(s) || isBareName(s) || isTuplePat(s) || consAt(s, 0) >= 0 || asAt(s, 0) >= 0 def inferMatchRest(first: Out, rest: List[Arm], scrutTy: String, env: List[(String, Ty)], funs: Ftab, ens: List[En]): Out = if (hasErr(first)) first else if (List.isEmpty(rest)) first else inferMatchArm2(first, inferMatchArm(List.at(rest, 0), scrutTy, env, funs, ens), List.at(rest, 0), List.tail(rest), scrutTy, env, funs, ens) @@ -1257,7 +1254,7 @@ def bindPat(pat: String, scrutTy: String, env: List[(String, Ty)], ens: List[En] if (Parse.patternAlternativeAt(pat, 0, 0) >= 0) bindPat(Str.take(pat, Parse.patternAlternativeAt(pat, 0, 0)), scrutTy, env, ens) else bindPat2(patBind(pat), pat, scrutTy, env, ens) def bindPat2(b: String, pat: String, scrutTy: String, env: List[(String, Ty)], ens: List[En]): List[(String, Ty)] = - if (asAt(pat, 0) >= 0) bindAs(pat, scrutTy, env, ens) else if (consAt(pat, 0) >= 0) bindCons(pat, scrutTy, env, ens) else if (isTuplePat(pat)) bindTuple(pat, scrutTy, env) else if (hasComma(b) || eqAt(b, 0) >= 0) bindConstructorFields(splitComma(b), constructorFields(specializeEnums(ens, scrutTy), patCore(pat)), 0, env, ens) else if (asAt(b, 0) >= 0) bindAs(b, bindTy(pat, scrutTy, ens), env, ens) else if (eqAt(b, 0) >= 0) bindEq(b, bindTy(pat, scrutTy, ens), env, ens) else if (consAt(b, 0) >= 0) bindCons(b, bindTy(pat, scrutTy, ens), env, ens) else if (b == "") bindBare(pat, scrutTy, env) else envBind(b, bindTy(pat, scrutTy, ens), env) + if (asAt(pat, 0) >= 0) bindAs(pat, scrutTy, env, ens) else if (consAt(pat, 0) >= 0) bindCons(pat, scrutTy, env, ens) else if (isTuplePat(pat)) bindTuple(pat, scrutTy, env, ens) else if (hasComma(b) || eqAt(b, 0) >= 0) bindConstructorFields(splitComma(b), constructorFields(specializeEnums(ens, scrutTy), patCore(pat)), 0, env, ens) else if (asAt(b, 0) >= 0) bindAs(b, bindTy(pat, scrutTy, ens), env, ens) else if (eqAt(b, 0) >= 0) bindEq(b, bindTy(pat, scrutTy, ens), env, ens) else if (consAt(b, 0) >= 0) bindCons(b, bindTy(pat, scrutTy, ens), env, ens) else if (isTuplePat(b)) bindTuple(b, bindTy(pat, scrutTy, ens), env, ens) else if (b == "") bindBare(pat, scrutTy, env) else envBind(b, bindTy(pat, scrutTy, ens), env) def constructorFields(ens: List[En], core: String): List[Param] = if (List.exists(ens, en => en.name == core)) List.flatMap(List.filter(ens, en => en.name == core), en => List.flatMap(en.cases, c => c.fields)) else List.flatMap(ens, en => constructorFieldsIn(en, core)) @@ -1296,7 +1293,7 @@ def bindConsAt(i: Int, pat: String, scrutTy: String, env: List[(String, Ty)], en bindPat(Str.slice(pat, i + 4, Str.len(pat)), scrutTy, bindPat(Str.slice(pat, 0, i), elemOf(scrutTy), env, ens), ens) def bindBare(pat: String, scrutTy: String, env: List[(String, Ty)]): List[(String, Ty)] = - if (isBareName(pat)) envBind(pat, scrutTy, env) else env + if (isBareName(pat) && dotAt(pat, 0) < 0) envBind(pat, scrutTy, env) else env def isBareName(s: String): Bool = Str.len(s) > 0 && isBareStart(Str.charAt(s, 0)) && s != "_" @@ -1325,8 +1322,8 @@ def bindAsAt(i: Int, pat: String, scrutTy: String, env: List[(String, Ty)], ens: def isTuplePat(pat: String): Bool = Str.len(pat) > 0 && Str.charAt(pat, 0) == 40 -def bindTuple(pat: String, scrutTy: String, env: List[(String, Ty)]): List[(String, Ty)] = - bindTuple2(splitComma(Str.slice(pat, 1, Str.len(pat) - 1)), Type.tupPartsStr(scrutTy), env) +def bindTuple(pat: String, scrutTy: String, env: List[(String, Ty)], ens: List[En]): List[(String, Ty)] = + bindTuple2(splitComma(Str.slice(pat, 1, Str.len(pat) - 1)), Type.tupPartsStr(scrutTy), env, ens) def splitComma(s: String): List[String] = splitCommaAt(s, commaAt(s, 0)) @@ -1349,11 +1346,8 @@ def trimName(s: String): String = def trimName2(s: String, a: Int, b: Int): String = if (a >= b) "" else if (Str.charAt(s, a) == 32) trimName2(s, a + 1, b) else if (Str.charAt(s, b - 1) == 32) trimName2(s, a, b - 1) else Str.slice(s, a, b) -def bindTuple2(ns: List[String], ts: List[String], env: List[(String, Ty)]): List[(String, Ty)] = - if (List.isEmpty(ns)) env else bindSlot(List.at(ns, 0), if (List.isEmpty(ts)) "A" else List.at(ts, 0), bindTuple2(List.tail(ns), if (List.isEmpty(ts)) ts else List.tail(ts), env)) - -def bindSlot(n: String, ty: String, env: List[(String, Ty)]): List[(String, Ty)] = - if (isTuplePat(n)) bindTuple(n, ty, env) else if (altStrPat(n) || altIntPat(n) || altBoolPat(n)) env else envBind(n, ty, env) +def bindTuple2(ns: List[String], ts: List[String], env: List[(String, Ty)], ens: List[En]): List[(String, Ty)] = + if (List.isEmpty(ns)) env else bindPat(List.at(ns, 0), if (List.isEmpty(ts)) "A" else List.at(ts, 0), bindTuple2(List.tail(ns), if (List.isEmpty(ts)) ts else List.tail(ts), env, ens), ens) def bindTy(pat: String, scrutTy: String, ens: List[En]): String = bindTyGot(patCore(pat), fieldTy(specializeEnums(ens, scrutTy), patCore(pat)), scrutTy) diff --git a/examples/compiler/src/Emit.scuzz b/examples/compiler/src/Emit.scuzz index d4792a77..665b5d98 100644 --- a/examples/compiler/src/Emit.scuzz +++ b/examples/compiler/src/Emit.scuzz @@ -3875,7 +3875,7 @@ def emitTryPat(pat: String, prefix: String, i: Int, scrut: String, ens: List[En] if (isAsPat(pat)) emitTryPat(stripAs(pat), prefix, i, scrut, ens, strs, last, tys) else emitTryPat2(pat, prefix, i, scrut, ens, strs, last, tys) def emitTryPat2(pat: String, prefix: String, i: Int, scrut: String, ens: List[En], strs: List[String], last: Bool, tys: List[String]): String = - if (pat == "_") line(Str.concat("br label %", Str.concat(prefix, Str.concat("_ok_0_", Str.fromInt(i))))) else if (pat == "[]") emitNilTry(prefix, i, scrut, last) else if (isConsPat(pat)) emitConsTry(prefix, i, scrut, last, slotInt(tys, 0), consTail(pat)) else if (isOrPat(pat)) emitOrTry(pat, prefix, i, scrut, ens, strs, last) else if (isStrPat(pat)) emitStrTry(pat, prefix, i, scrut, strs, last) else if (isIntPat(pat)) emitIntTry(pat, prefix, i, scrut, last) else if (isBoolPat(pat)) emitIntTry(boolPatLit(pat), prefix, i, scrut, last) else if (isTuplePat(pat)) emitTupleTry(pat, prefix, i, scrut, strs, last, tys) else if (isVarPat(pat, ens)) line(Str.concat("br label %", Str.concat(prefix, Str.concat("_ok_0_", Str.fromInt(i))))) else if (isConsPat(patBind(pat))) emitCtorConsTry(pat, prefix, i, scrut, ens, last, tys) else join(emitTag(prefix, i, scrut, patTag(pat, ens)), join(emitTagBr(prefix, i, last), emitLiteralMatchedPat(pat, prefix, i, scrut, ens, strs, last))) + if (pat == "_") line(Str.concat("br label %", Str.concat(prefix, Str.concat("_ok_0_", Str.fromInt(i))))) else if (pat == "[]") emitNilTry(prefix, i, scrut, last) else if (isConsPat(pat)) emitConsTry(prefix, i, scrut, last, slotInt(tys, 0), consTail(pat)) else if (isOrPat(pat)) emitOrTry(pat, prefix, i, scrut, ens, strs, last) else if (isStrPat(pat)) emitStrTry(pat, prefix, i, scrut, strs, last) else if (isIntPat(pat)) emitIntTry(pat, prefix, i, scrut, last) else if (isBoolPat(pat)) emitIntTry(boolPatLit(pat), prefix, i, scrut, last) else if (isTuplePat(pat)) emitTupleTry(pat, prefix, i, scrut, strs, last, tys, ens) else if (isVarPat(pat, ens)) line(Str.concat("br label %", Str.concat(prefix, Str.concat("_ok_0_", Str.fromInt(i))))) else if (isConsPat(patBind(pat))) emitCtorConsTry(pat, prefix, i, scrut, ens, last, tys) else join(emitTag(prefix, i, scrut, patTag(pat, ens)), join(emitTagBr(prefix, i, last), emitLiteralMatchedPat(pat, prefix, i, scrut, ens, strs, last))) def emitCtorConsTry(pat: String, prefix: String, i: Int, scrut: String, ens: List[En], last: Bool, tys: List[String]): String = join(emitTag(prefix, i, scrut, patTag(pat, ens)), join(emitTagBr(prefix, i, last), emitCtorConsM(pat, prefix, i, scrut, last, tys))) @@ -3925,8 +3925,8 @@ def emitConsOk(prefix: String, i: Int): String = def isTuplePat(pat: String): Bool = Str.len(pat) > 0 && Str.charAt(pat, 0) == 40 -def emitTupleTry(pat: String, prefix: String, i: Int, scrut: String, strs: List[String], last: Bool, tys: List[String]): String = - emitTuplePeel(ppre(prefix, i), i + 1, scrut, tys, tupComps(pat), prefix, i, last, strs, true) +def emitTupleTry(pat: String, prefix: String, i: Int, scrut: String, strs: List[String], last: Bool, tys: List[String], ens: List[En]): String = + emitTuplePeel(ppre(prefix, i), i + 1, scrut, tys, tupComps(pat), prefix, i, last, strs, true, ens) def tupComps(pat: String): List[String] = splitPat(Str.slice(pat, 1, Str.len(pat) - 1)) @@ -3940,23 +3940,23 @@ def nestComps(comp: String): List[String] = def tailComps(comps: List[String]): List[String] = if (List.isEmpty(comps)) comps else List.tail(comps) -def emitTuplePeel(pre: String, sid: Int, scrut: String, tys: List[String], comps: List[String], armPrefix: String, armI: Int, last: Bool, strs: List[String], fin: Bool): String = - join(peelLeft(pre, sid, scrut), join(peelRight(pre, sid, scrut), join(peelBrTn(pre, sid), join(peelTnLab(pre, sid), emitTupleCont(pre, sid, tys, comps, armPrefix, armI, last, strs, fin))))) +def emitTuplePeel(pre: String, sid: Int, scrut: String, tys: List[String], comps: List[String], armPrefix: String, armI: Int, last: Bool, strs: List[String], fin: Bool, ens: List[En]): String = + join(peelLeft(pre, sid, scrut), join(peelRight(pre, sid, scrut), join(peelBrTn(pre, sid), join(peelTnLab(pre, sid), emitTupleCont(pre, sid, tys, comps, armPrefix, armI, last, strs, fin, ens))))) -def emitTupleCont(pre: String, sid: Int, tys: List[String], comps: List[String], armPrefix: String, armI: Int, last: Bool, strs: List[String], fin: Bool): String = - join(if (slotInt(tys, 0)) peelUnboxL(pre, sid) else "", tupleSideTest(compAt(comps, 0), pre, sid, "l", slotInt(tys, 0), strs, emitTupleRest(pre, sid, tys, comps, armPrefix, armI, last, strs, fin), nextLab(armPrefix, armI, last))) +def emitTupleCont(pre: String, sid: Int, tys: List[String], comps: List[String], armPrefix: String, armI: Int, last: Bool, strs: List[String], fin: Bool, ens: List[En]): String = + join(if (slotInt(tys, 0)) peelUnboxL(pre, sid) else "", tupleSideTest(compAt(comps, 0), pre, sid, "l", slotInt(tys, 0), strs, emitTupleRest(pre, sid, tys, comps, armPrefix, armI, last, strs, fin, ens), nextLab(armPrefix, armI, last), ens, headTy(tys), armPrefix, armI, last)) -def emitTupleRest(pre: String, sid: Int, tys: List[String], comps: List[String], armPrefix: String, armI: Int, last: Bool, strs: List[String], fin: Bool): String = - join(emitNest(pre, sid, "tl", headTy(tys), compAt(comps, 0), armPrefix, armI, last, strs), emitTupleRest2(pre, sid, tys, comps, armPrefix, armI, last, strs, fin)) +def emitTupleRest(pre: String, sid: Int, tys: List[String], comps: List[String], armPrefix: String, armI: Int, last: Bool, strs: List[String], fin: Bool, ens: List[En]): String = + join(emitNest(pre, sid, "tl", headTy(tys), compAt(comps, 0), armPrefix, armI, last, strs, ens), emitTupleRest2(pre, sid, tys, comps, armPrefix, armI, last, strs, fin, ens)) -def emitTupleRest2(pre: String, sid: Int, tys: List[String], comps: List[String], armPrefix: String, armI: Int, last: Bool, strs: List[String], fin: Bool): String = - if (List.len(tys) > 2) emitTuplePeel(Str.concat(pre, Str.concat("_n", Str.fromInt(sid))), sid + 1, pct(pre, Str.concat("tr", Str.fromInt(sid))), List.tail(tys), tailComps(comps), armPrefix, armI, last, strs, fin) else join(emitNest(pre, sid, "tr", secondTy(tys), compAt(comps, 1), armPrefix, armI, last, strs), join(if (slotInt(tys, 1)) peelUnboxR(pre, sid) else "", tupleSideTest(compAt(comps, 1), pre, sid, "r", slotInt(tys, 1), strs, if (fin) tupleBrOk(armPrefix, armI) else "", nextLab(armPrefix, armI, last)))) +def emitTupleRest2(pre: String, sid: Int, tys: List[String], comps: List[String], armPrefix: String, armI: Int, last: Bool, strs: List[String], fin: Bool, ens: List[En]): String = + if (List.len(tys) > 2) emitTuplePeel(Str.concat(pre, Str.concat("_n", Str.fromInt(sid))), sid + 1, pct(pre, Str.concat("tr", Str.fromInt(sid))), List.tail(tys), tailComps(comps), armPrefix, armI, last, strs, fin, ens) else join(emitNest(pre, sid, "tr", secondTy(tys), compAt(comps, 1), armPrefix, armI, last, strs, ens), join(if (slotInt(tys, 1)) peelUnboxR(pre, sid) else "", tupleSideTest(compAt(comps, 1), pre, sid, "r", slotInt(tys, 1), strs, if (fin) tupleBrOk(armPrefix, armI) else "", nextLab(armPrefix, armI, last), ens, secondTy(tys), armPrefix, armI, last))) -def emitNest(pre: String, sid: Int, side: String, ty: String, comp: String, armPrefix: String, armI: Int, last: Bool, strs: List[String]): String = - if (!isTupTy(ty)) "" else emitTuplePeel(Str.concat(pre, Str.concat(Str.concat("_", side), Str.fromInt(sid))), 1, pct(pre, Str.concat(side, Str.fromInt(sid))), tupSlots(ty), nestComps(comp), armPrefix, armI, last, strs, false) +def emitNest(pre: String, sid: Int, side: String, ty: String, comp: String, armPrefix: String, armI: Int, last: Bool, strs: List[String], ens: List[En]): String = + if (!isTupTy(ty)) "" else emitTuplePeel(Str.concat(pre, Str.concat(Str.concat("_", side), Str.fromInt(sid))), 1, pct(pre, Str.concat(side, Str.fromInt(sid))), tupSlots(ty), nestComps(comp), armPrefix, armI, last, strs, false, ens) -def tupleSideTest(comp: String, pre: String, sid: Int, side: String, isInt: Bool, strs: List[String], cont: String, fail: String): String = - if (isIntPat(comp) || isBoolPat(comp)) tupleIntTest(comp, pre, sid, side, isInt, cont, fail) else if (isStrPat(comp)) tupleStrTest(comp, pre, sid, side, strs, cont, fail) else cont +def tupleSideTest(comp: String, pre: String, sid: Int, side: String, isInt: Bool, strs: List[String], cont: String, fail: String, ens: List[En], ty: String, armPrefix: String, armI: Int, last: Bool): String = + if (isIntPat(comp) || isBoolPat(comp)) tupleIntTest(comp, pre, sid, side, isInt, cont, fail) else if (isStrPat(comp)) tupleStrTest(comp, pre, sid, side, strs, cont, fail) else if (isTuplePat(comp)) cont else join(emitFieldTest(comp, if (isInt) tupleSideI64(pre, sid, side) else tupleSideVal(pre, sid, side), ty, armPrefix, armI, ens, strs, last), cont) def tupleContStem(pre: String, side: String): String = Str.concat(pre, Str.concat("_tok", side)) @@ -4174,16 +4174,52 @@ def emitLiteralMatchedPat(pat: String, prefix: String, i: Int, scrut: String, en join(lab(Str.concat(ppre(prefix, i), Str.concat("_m", sid(i)))), join(emitPayload(pat, prefix, i, scrut, ens), emitLiteralFields(splitPat(patBind(pat)), pat, prefix, i, ens, strs, last, 0))) def emitLiteralFields(fields: List[String], pat: String, prefix: String, i: Int, ens: List[En], strs: List[String], last: Bool, k: Int): String = - if (List.isEmpty(fields)) emitMatchedEnd(pat, prefix, i) else join(emitLiteralField(fieldBind(List.at(fields, 0)), literalFieldValue(pat, List.at(fields, 0), prefix, i, k, ens), prefix, i, k, strs, last), emitLiteralFields(List.tail(fields), pat, prefix, i, ens, strs, last, k + 1)) + join(emitFieldTests(fields, pat, prefix, i, ens, strs, last, k, prefix, i), emitMatchedEnd(pat, prefix, i)) + +def emitFieldTests(fields: List[String], pat: String, prefix: String, i: Int, ens: List[En], strs: List[String], last: Bool, k: Int, armPrefix: String, armI: Int): String = + if (List.isEmpty(fields)) "" else join(emitFieldTest(fieldBind(List.at(fields, 0)), literalFieldValue(pat, List.at(fields, 0), prefix, i, k, ens), fieldTyAt(pat, List.at(fields, 0), k, ens), armPrefix, armI, ens, strs, last), emitFieldTests(List.tail(fields), pat, prefix, i, ens, strs, last, k + 1, armPrefix, armI)) + +def fieldTyAt(pat: String, field: String, k: Int, ens: List[En]): String = + fieldTyAt2(payloadTys(ens, patCore(pat)), fieldIdx(field, k, payloadNames(ens, patCore(pat)))) + +def fieldTyAt2(fts: List[String], k: Int): String = + if (k < 0 || k >= List.len(fts)) "" else List.at(fts, k) + +def nestPreOf(v: String): String = + Str.concat(ssaName(v), "_n") + +def isCtorPat(pat: String, ens: List[En]): Bool = + if (isAsPat(pat) || isConsPat(pat) || isTuplePat(pat) || isOrPat(pat) || isStrPat(pat) || isIntPat(pat) || isBoolPat(pat) || pat == "_" || pat == "[]" || pat == "") false else patBind(pat) != "" || dotAt(patCore(pat), 0) >= 0 || enOfCase(ens, patCore(pat)) != "" + +def emitFieldTest(pat: String, value: String, ty: String, armPrefix: String, armI: Int, ens: List[En], strs: List[String], last: Bool): String = + if (isAsPat(pat)) emitFieldTest(stripAs(pat), value, ty, armPrefix, armI, ens, strs, last) else if (isStrPat(pat) || isIntPat(pat) || isBoolPat(pat)) literalFieldTestTo(pat, value, nestPreOf(value), 0, "lit", strs, nextLab(armPrefix, armI, last)) else if (pat == "[]") emitNestedNil(value, nestPreOf(value), nextLab(armPrefix, armI, last)) else if (isConsPat(pat)) emitNestedCons(pat, value, nestPreOf(value), armPrefix, armI, last, ens, strs, ty) else if (isTuplePat(pat)) emitTuplePeel(ssaName(value), 1, value, tupSlots(ty), tupComps(pat), armPrefix, armI, last, strs, false, ens) else if (isCtorPat(pat, ens)) emitNestedCtor(pat, value, nestPreOf(value), armPrefix, armI, last, ens, strs) else "" + +def emitNestedNil(value: String, pre: String, fail: String): String = + join(line(Str.concat(tmp(pre, "ne"), Str.concat("call i32 @sz_list_is_empty(ptr ", Str.concat(value, ")")))), join(line(Str.concat(tmp(pre, "nc"), Str.concat("icmp ne i32 ", Str.concat(pct(pre, "ne"), ", 0")))), join(line(Str.concat("br i1 ", Str.concat(pct(pre, "nc"), Str.concat(", label %", Str.concat(pre, Str.concat("_ok, label %", fail)))))), lab(Str.concat(pre, "_ok"))))) + +def emitNestedCons(pat: String, value: String, pre: String, armPrefix: String, armI: Int, last: Bool, ens: List[En], strs: List[String], ty: String): String = + join(emitConsEmpty(pre, 0, value), join(emitConsBrTo(pre, 0, nextLab(armPrefix, armI, last)), join(lab(Str.concat(ppre(pre, 0), "_cm")), join(emitConsLoad(pre, 0, value, isI64Ty(Check.elemOf(ty))), join(emitFieldTest(consHead(pat), Str.concat("%", consHeadStem(pre, 0, isI64Ty(Check.elemOf(ty)))), Check.elemOf(ty), armPrefix, armI, ens, strs, last), emitFieldTest(consTail(pat), pct(ppre(pre, 0), "ct"), ty, armPrefix, armI, ens, strs, last)))))) + +def emitConsBrTo(prefix: String, i: Int, fail: String): String = + line(Str.concat("br i1 ", Str.concat(pct(ppre(prefix, i), "cc"), Str.concat(", label %", Str.concat(ppre(prefix, i), Str.concat("_cm, label %", fail)))))) + +def emitNestedCtor(pat: String, value: String, pre: String, armPrefix: String, armI: Int, last: Bool, ens: List[En], strs: List[String]): String = + join(emitTag(pre, 0, value, patTag(pat, ens)), join(emitTagBrTo(pre, 0, nextLab(armPrefix, armI, last)), join(lab(Str.concat(ppre(pre, 0), Str.concat("_m", sid(0)))), join(emitPayload(pat, pre, 0, value, ens), emitFieldTests(splitPat(patBind(pat)), pat, pre, 0, ens, strs, last, 0, armPrefix, armI))))) + +def emitTagBrTo(prefix: String, i: Int, fail: String): String = + line(Str.concat("br i1 ", Str.concat(pct(ppre(prefix, i), Str.concat("eq", sid(i))), Str.concat(", label %", Str.concat(ppre(prefix, i), Str.concat("_m", Str.concat(sid(i), Str.concat(", label %", fail)))))))) + +def literalFieldTestTo(pat: String, value: String, prefix: String, i: Int, tag: String, strs: List[String], fail: String): String = + join(literalFieldCompare(pat, value, prefix, i, tag, strs), join(line(Str.concat("br i1 ", Str.concat(pct(ppre(prefix, i), Str.concat("seq", Str.concat(sid(i), tag))), Str.concat(", label %", Str.concat(ppre(prefix, i), Str.concat(tag, Str.concat(", label %", fail))))))), lab(Str.concat(ppre(prefix, i), tag)))) def literalFieldValue(pat: String, field: String, prefix: String, i: Int, k: Int, ens: List[En]): String = if (hasComma(patBind(pat)) || namedMulti(pat, ens)) Str.concat("%", fieldStem(prefix, i, fieldIdx(field, k, payloadNames(ens, patCore(pat))), slotInt(payloadTys(ens, patCore(pat)), fieldIdx(field, k, payloadNames(ens, patCore(pat)))))) else Str.concat("%", bindStem(prefix, i, bindIsInt(pat, ens))) def emitLiteralField(pat: String, value: String, prefix: String, i: Int, k: Int, strs: List[String], last: Bool): String = - if (isStrPat(pat) || isIntPat(pat) || isBoolPat(pat)) literalFieldTest(pat, value, prefix, i, Str.concat("lit", Str.fromInt(k)), strs, last) else "" + if (isStrPat(pat) || isIntPat(pat) || isBoolPat(pat)) literalFieldTestTo(pat, value, prefix, i, Str.concat("lit", Str.fromInt(k)), strs, nextLab(prefix, i, last)) else "" def literalFieldTest(pat: String, value: String, prefix: String, i: Int, tag: String, strs: List[String], last: Bool): String = - join(literalFieldCompare(pat, value, prefix, i, tag, strs), join(line(Str.concat("br i1 ", Str.concat(pct(ppre(prefix, i), Str.concat("seq", Str.concat(sid(i), tag))), Str.concat(", label %", Str.concat(ppre(prefix, i), Str.concat(tag, Str.concat(", label %", nextLab(prefix, i, last)))))))), lab(Str.concat(ppre(prefix, i), tag)))) + literalFieldTestTo(pat, value, prefix, i, tag, strs, nextLab(prefix, i, last)) def literalFieldCompare(pat: String, value: String, prefix: String, i: Int, tag: String, strs: List[String]): String = if (isStrPat(pat)) literalStringCompare(unquote(pat), value, prefix, i, tag, strs) else line(Str.concat(tmp(ppre(prefix, i), Str.concat("seq", Str.concat(sid(i), tag))), Str.concat("icmp eq i64 ", Str.concat(value, Str.concat(", ", if (isBoolPat(pat)) boolPatLit(pat) else pat))))) @@ -4247,7 +4283,7 @@ def emitOkVar2(alias: String, s: Slot, asI64: Bool): Slot = } def emitOkSlotPat2(pat: String, body: Expr, prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, i: Int, tys: List[String], fid: Int): Slot = - if (isOrPat(pat)) emitOkSlotPat2(orLeft(pat), body, prefix, strs, defs, ens, ps, loc, i, tys, fid) else if (isConsPat(pat)) emitOkCons(pat, body, prefix, strs, defs, ens, ps, loc, i, tys, fid) else if (isConsPat(patBind(pat))) emitOkCons(patBind(pat), body, prefix, strs, defs, ens, ps, loc, i, tys, fid) else if (isTuplePat(pat)) emitOkTuple(pat, body, prefix, strs, defs, ens, ps, loc, i, tys, fid) else if (hasComma(patBind(pat)) || namedMulti(pat, ens)) emitOkFields(pat, body, prefix, strs, defs, ens, ps, loc, i, fid) else emitOkSlotBind(fieldBind(patBind(pat)), bindIsInt(pat, ens), body, prefix, strs, defs, ens, patternPayloadParams(pat, prefix, i, ens, ps), loc, i, fid) + if (isOrPat(pat)) emitOkSlotPat2(orLeft(pat), body, prefix, strs, defs, ens, ps, loc, i, tys, fid) else if (isConsPat(pat)) emitOkCons(pat, body, prefix, strs, defs, ens, ps, loc, i, tys, fid) else if (isConsPat(patBind(pat))) emitOkCons(patBind(pat), body, prefix, strs, defs, ens, ps, loc, i, tys, fid) else if (isTuplePat(pat)) emitOkTuple(pat, body, prefix, strs, defs, ens, ps, loc, i, tys, fid) else if (hasComma(patBind(pat)) || namedMulti(pat, ens)) emitOkFields(pat, body, prefix, strs, defs, ens, ps, loc, i, fid) else emitOkNestedBind(fieldBind(patBind(pat)), bindStem(prefix, i, bindIsInt(pat, ens)), fieldTy(ens, patCore(pat)), body, prefix, strs, defs, ens, ps, loc, i, fid) def patternPayloadParams(pat: String, prefix: String, i: Int, ens: List[En], ps: List[Param]): List[Param] = if (!isPatternName(fieldBind(patBind(pat)))) ps else Param(bindStem(prefix, i, bindIsInt(pat, ens)), fieldTy(ens, patCore(pat)), "", "") :: ps @@ -4324,24 +4360,27 @@ def patternParam(bind: String, stem: String, ty: String, ps: List[Param]): List[ if (!keepBind(bind) || bind == "[]") ps else Param(stem, ty, "", "") :: ps def emitOkFields(pat: String, body: Expr, prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, i: Int, fid: Int): Slot = - emitOkPatternBody(rewriteFields(splitPat(patBind(pat)), body, prefix, i, 0, payloadTys(ens, patCore(pat)), payloadNames(ens, patCore(pat))), Str.concat(prefix, Str.concat("_a0_", Str.fromInt(i))), strs, defs, ens, fieldPayloadParams(splitPat(patBind(pat)), payloadTys(ens, patCore(pat)), payloadNames(ens, patCore(pat)), prefix, i, 0, ps), loc, fid) + emitOkPatternBody(rewriteFields(splitPat(patBind(pat)), body, prefix, i, 0, payloadTys(ens, patCore(pat)), payloadNames(ens, patCore(pat)), ens), Str.concat(prefix, Str.concat("_a0_", Str.fromInt(i))), strs, defs, ens, fieldPayloadParams(splitPat(patBind(pat)), payloadTys(ens, patCore(pat)), payloadNames(ens, patCore(pat)), prefix, i, 0, ens, ps), loc, fid) -def fieldPayloadParams(ns: List[String], fts: List[String], names: List[String], prefix: String, i: Int, k: Int, ps: List[Param]): List[Param] = - if (List.isEmpty(ns)) ps else fieldPayloadParams(List.tail(ns), fts, names, prefix, i, k + 1, fieldPayloadParam(List.at(ns, 0), fts, names, prefix, i, k, ps)) +def fieldPayloadParams(ns: List[String], fts: List[String], names: List[String], prefix: String, i: Int, k: Int, ens: List[En], ps: List[Param]): List[Param] = + if (List.isEmpty(ns)) ps else fieldPayloadParams(List.tail(ns), fts, names, prefix, i, k + 1, ens, fieldPayloadParam(List.at(ns, 0), fts, names, prefix, i, k, ens, ps)) -def fieldPayloadParam(name: String, fts: List[String], names: List[String], prefix: String, i: Int, k: Int, ps: List[Param]): List[Param] = - if (!isPatternName(fieldBind(name))) ps else fieldPayloadParamAt(fts, prefix, i, fieldIdx(name, k, names), ps) +def fieldPayloadParam(name: String, fts: List[String], names: List[String], prefix: String, i: Int, k: Int, ens: List[En], ps: List[Param]): List[Param] = + nestBps(fieldBind(name), fieldStem(prefix, i, fieldIdx(name, k, names), slotInt(fts, fieldIdx(name, k, names))), fieldTyAt2(fts, fieldIdx(name, k, names)), ens, ps) -def fieldPayloadParamAt(fts: List[String], prefix: String, i: Int, k: Int, ps: List[Param]): List[Param] = - Param(fieldStem(prefix, i, k, slotInt(fts, k)), List.at(fts, k), "", "") :: ps +def nestBps(pat: String, stem: String, ty: String, ens: List[En], ps: List[Param]): List[Param] = + if (isAsPat(pat)) nestBps(stripAs(pat), stem, ty, ens, patternParam(asName(pat), stem, ty, ps)) else if (isTuplePat(pat)) stemBpsAt(tupComps(pat), ssaName(stem), 1, tupSlots(ty), ens, ps) else if (isConsPat(pat)) consPayloadParams(pat, nestPreOf(stem), 0, Check.elemOf(ty) :: noStr(), ps) else if (isCtorPat(pat, ens)) fieldPayloadParams(splitPat(patBind(pat)), payloadTys(ens, patCore(pat)), payloadNames(ens, patCore(pat)), nestPreOf(stem), 0, 0, ens, ps) else if (pat == "[]" || pat == "_" || !isPatternName(pat)) ps else Param(stem, ty, "", "") :: dropParam(ps, stem) def emitOkOwned(s: Slot, ps: List[Param]): Slot = s match { case Slot(code, val, owned) => if (owned || isIntSsa(val) || !isPtrSlot(val, ps)) s else Slot(join(code, retPtr(val)), val, true) } -def rewriteFields(ns: List[String], body: Expr, prefix: String, i: Int, k: Int, fts: List[String], names: List[String]): Expr = - if (List.isEmpty(ns)) body else rewriteFields(List.tail(ns), rewriteBind(body, fieldBind(List.at(ns, 0)), fieldStem(prefix, i, fieldIdx(List.at(ns, 0), k, names), slotInt(fts, fieldIdx(List.at(ns, 0), k, names)))), prefix, i, k + 1, fts, names) +def rewriteFields(ns: List[String], body: Expr, prefix: String, i: Int, k: Int, fts: List[String], names: List[String], ens: List[En]): Expr = + if (List.isEmpty(ns)) body else rewriteFields(List.tail(ns), rewriteNested(fieldBind(List.at(ns, 0)), fieldStem(prefix, i, fieldIdx(List.at(ns, 0), k, names), slotInt(fts, fieldIdx(List.at(ns, 0), k, names))), fieldTyAt2(fts, fieldIdx(List.at(ns, 0), k, names)), ens, body), prefix, i, k + 1, fts, names, ens) + +def rewriteNested(pat: String, stem: String, ty: String, ens: List[En], body: Expr): Expr = + if (isAsPat(pat)) rewriteBind(rewriteNested(stripAs(pat), stem, ty, ens, body), asName(pat), stem) else if (isTuplePat(pat)) rewriteTupleAt(tupComps(pat), body, ssaName(stem), 1, tupSlots(ty), ens) else if (isConsPat(pat)) rewriteCons(pat, body, nestPreOf(stem), 0, isI64Ty(Check.elemOf(ty))) else if (isCtorPat(pat, ens)) rewriteFields(splitPat(patBind(pat)), body, nestPreOf(stem), 0, 0, payloadTys(ens, patCore(pat)), payloadNames(ens, patCore(pat)), ens) else if (pat == "_" || isPatternName(pat) && pat != "[]") rewriteBind(body, pat, stem) else body def fieldIdx(h: String, k: Int, names: List[String]): Int = if (isNamedPat(h)) idxOf(names, fieldNameOf(h)) else k @@ -4378,7 +4417,7 @@ def fieldStem(prefix: String, i: Int, k: Int, isInt: Bool): String = Str.concat(fpre(prefix, i), Str.concat(if (isInt) "_b" else "_c", Str.fromInt(k))) def emitOkTuple(pat: String, body: Expr, prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, i: Int, tys: List[String], fid: Int): Slot = - emitOkTuple2(pat, rewriteTuple(pat, body, prefix, i, tys), prefix, strs, defs, ens, loc, i, stemBps(pat, prefix, i, tys, ps), fid) + emitOkTuple2(pat, rewriteTuple(pat, body, prefix, i, tys, ens), prefix, strs, defs, ens, loc, i, stemBps(pat, prefix, i, tys, ens, ps), fid) def emitOkTuple2(pat: String, body: Expr, prefix: String, strs: List[String], defs: Ftab, ens: List[En], loc: String, i: Int, bps: List[Param], fid: Int): Slot = emitOkTupleJoin(emitOkOwned(emitExprFid(body, Str.concat(prefix, Str.concat("_a0_", Str.fromInt(i))), strs, defs, ens, bps, loc, fid), bps), exprIsI64(body, bps, defs)) @@ -4388,32 +4427,32 @@ def emitOkTupleJoin(s: Slot, asI64: Bool): Slot = case Slot(code, val, owned) => Slot(code, val, owned || !asI64) } -def stemBps(pat: String, prefix: String, i: Int, tys: List[String], ps: List[Param]): List[Param] = - stemBpsAt(splitPat(Str.slice(pat, 1, Str.len(pat) - 1)), ppre(prefix, i), i + 1, tys, ps) +def stemBps(pat: String, prefix: String, i: Int, tys: List[String], ens: List[En], ps: List[Param]): List[Param] = + stemBpsAt(splitPat(Str.slice(pat, 1, Str.len(pat) - 1)), ppre(prefix, i), i + 1, tys, ens, ps) -def stemBpsAt(ns: List[String], pre: String, sid: Int, tys: List[String], ps: List[Param]): List[Param] = - if (List.len(ns) < 2) ps else if (List.len(ns) == 2) addStem(List.at(ns, 0), leftStem(pre, sid, tys), stemTy(tys, 0), addStem(List.at(List.tail(ns), 0), rightStem(pre, sid, tys), stemTy(tys, 1), ps)) else addStem(List.at(ns, 0), leftStem(pre, sid, tys), stemTy(tys, 0), stemBpsAt(List.tail(ns), Str.concat(pre, Str.concat("_n", Str.fromInt(sid))), sid + 1, if (List.isEmpty(tys)) tys else List.tail(tys), ps)) +def stemBpsAt(ns: List[String], pre: String, sid: Int, tys: List[String], ens: List[En], ps: List[Param]): List[Param] = + if (List.len(ns) < 2) ps else if (List.len(ns) == 2) addStem(List.at(ns, 0), leftStem(pre, sid, tys), stemTy(tys, 0), ens, addStem(List.at(List.tail(ns), 0), rightStem(pre, sid, tys), stemTy(tys, 1), ens, ps)) else addStem(List.at(ns, 0), leftStem(pre, sid, tys), stemTy(tys, 0), ens, stemBpsAt(List.tail(ns), Str.concat(pre, Str.concat("_n", Str.fromInt(sid))), sid + 1, if (List.isEmpty(tys)) tys else List.tail(tys), ens, ps)) def stemTy(tys: List[String], k: Int): String = if (slotInt(tys, k)) "Int" else if (k <= 0) headOrAny(tys) else secondOrAny(tys) -def addStem(n: String, stem: String, ty: String, ps: List[Param]): List[Param] = - if (!keepBind(n) || isTuplePat(n)) ps else Param(stem, ty, "", "") :: dropParam(ps, stem) +def addStem(n: String, stem: String, ty: String, ens: List[En], ps: List[Param]): List[Param] = + nestBps(n, stem, ty, ens, ps) -def rewriteTuple(pat: String, body: Expr, prefix: String, i: Int, tys: List[String]): Expr = - rewriteTupleAt(splitPat(Str.slice(pat, 1, Str.len(pat) - 1)), body, ppre(prefix, i), i + 1, tys) +def rewriteTuple(pat: String, body: Expr, prefix: String, i: Int, tys: List[String], ens: List[En]): Expr = + rewriteTupleAt(splitPat(Str.slice(pat, 1, Str.len(pat) - 1)), body, ppre(prefix, i), i + 1, tys, ens) -def rewriteTupleAt(ns: List[String], body: Expr, pre: String, sid: Int, tys: List[String]): Expr = - if (List.isEmpty(ns)) body else rewriteTupleAt1(List.at(ns, 0), List.tail(ns), body, pre, sid, tys) +def rewriteTupleAt(ns: List[String], body: Expr, pre: String, sid: Int, tys: List[String], ens: List[En]): Expr = + if (List.isEmpty(ns)) body else rewriteTupleAt1(List.at(ns, 0), List.tail(ns), body, pre, sid, tys, ens) -def rewriteTupleAt1(n: String, rest: List[String], body: Expr, pre: String, sid: Int, tys: List[String]): Expr = - if (List.isEmpty(rest)) rewriteSlot(n, rightStem(pre, sid, tys), if (List.len(tys) > 1) secondTy(tys) else headTy(tys), body) else rewriteTupleAt2(n, List.at(rest, 0), List.tail(rest), body, pre, sid, tys) +def rewriteTupleAt1(n: String, rest: List[String], body: Expr, pre: String, sid: Int, tys: List[String], ens: List[En]): Expr = + if (List.isEmpty(rest)) rewriteSlot(n, rightStem(pre, sid, tys), if (List.len(tys) > 1) secondTy(tys) else headTy(tys), ens, body) else rewriteTupleAt2(n, List.at(rest, 0), List.tail(rest), body, pre, sid, tys, ens) -def rewriteTupleAt2(n: String, n2: String, more: List[String], body: Expr, pre: String, sid: Int, tys: List[String]): Expr = - if (List.isEmpty(more)) rewriteSlot(n, leftStem(pre, sid, tys), headTy(tys), rewriteSlot(n2, rightStem(pre, sid, tys), secondTy(tys), body)) else rewriteTupleAt1(n2, more, rewriteSlot(n, leftStem(pre, sid, tys), headTy(tys), body), Str.concat(pre, Str.concat("_n", Str.fromInt(sid))), sid + 1, if (List.isEmpty(tys)) tys else List.tail(tys)) +def rewriteTupleAt2(n: String, n2: String, more: List[String], body: Expr, pre: String, sid: Int, tys: List[String], ens: List[En]): Expr = + if (List.isEmpty(more)) rewriteSlot(n, leftStem(pre, sid, tys), headTy(tys), ens, rewriteSlot(n2, rightStem(pre, sid, tys), secondTy(tys), ens, body)) else rewriteTupleAt1(n2, more, rewriteSlot(n, leftStem(pre, sid, tys), headTy(tys), ens, body), Str.concat(pre, Str.concat("_n", Str.fromInt(sid))), sid + 1, if (List.isEmpty(tys)) tys else List.tail(tys), ens) -def rewriteSlot(n: String, stem: String, ty: String, body: Expr): Expr = - if (isTuplePat(n)) rewriteTupleAt(splitPat(Str.slice(n, 1, Str.len(n) - 1)), body, stem, 1, tupSlots(ty)) else rewriteBind(body, n, stem) +def rewriteSlot(n: String, stem: String, ty: String, ens: List[En], body: Expr): Expr = + rewriteNested(n, stem, ty, ens, body) def leftStem(pre: String, sid: Int, tys: List[String]): String = Str.concat(pre, Str.concat(if (slotInt(tys, 0)) "_tvl" else "_tl", Str.fromInt(sid))) @@ -4445,6 +4484,9 @@ def trimPat2(s: String, a: Int, b: Int): String = def emitOkSlotBind(bind: String, isInt: Bool, body: Expr, prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, i: Int, fid: Int): Slot = if (isAsPat(bind)) emitOkSlotBind(asName(bind), isInt, body, prefix, strs, defs, ens, ps, loc, i, fid) else emitOkPatternBody(if (bind == "") body else rewriteBind(body, bind, bindStem(prefix, i, isInt)), Str.concat(prefix, Str.concat("_a0_", Str.fromInt(i))), strs, defs, ens, ps, loc, fid) +def emitOkNestedBind(bind: String, stem: String, ty: String, body: Expr, prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, i: Int, fid: Int): Slot = + emitOkPatternBody(rewriteNested(bind, stem, ty, ens, body), Str.concat(prefix, Str.concat("_a0_", Str.fromInt(i))), strs, defs, ens, nestBps(bind, stem, ty, ens, ps), loc, fid) + def rewriteBind(e: Expr, from: String, to: String): Expr = if (from == "") e else rewriteBind1(e, from, to) @@ -4755,7 +4797,7 @@ def emitTailGuardExpr(pat: String, g: String, strs: List[String], defs: Ftab, en emitPatternGuard(pat, g, "body", strs, defs, ens, ps, loc, i) def emitPatternGuard(pat: String, g: String, prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, i: Int): Slot = - if (isOrPat(pat)) emitPatternGuard(orLeft(pat), g, prefix, strs, defs, ens, ps, loc, i) else if (hasComma(patBind(pat)) || namedMulti(pat, ens)) emitExpr(rewriteFields(splitPat(patBind(pat)), parseGuard(g), prefix, i, 0, payloadTys(ens, patCore(pat)), payloadNames(ens, patCore(pat))), Str.concat(prefix, Str.concat("_g0_", Str.fromInt(i))), strs, defs, ens, fieldPayloadParams(splitPat(patBind(pat)), payloadTys(ens, patCore(pat)), payloadNames(ens, patCore(pat)), prefix, i, 0, ps), loc) else emitExpr(rewriteBind(parseGuard(g), fieldBind(patBind(pat)), bindStem(prefix, i, bindIsInt(pat, ens))), Str.concat(prefix, Str.concat("_g0_", Str.fromInt(i))), strs, defs, ens, patternPayloadParams(pat, prefix, i, ens, ps), loc) + if (isOrPat(pat)) emitPatternGuard(orLeft(pat), g, prefix, strs, defs, ens, ps, loc, i) else if (hasComma(patBind(pat)) || namedMulti(pat, ens)) emitExpr(rewriteFields(splitPat(patBind(pat)), parseGuard(g), prefix, i, 0, payloadTys(ens, patCore(pat)), payloadNames(ens, patCore(pat)), ens), Str.concat(prefix, Str.concat("_g0_", Str.fromInt(i))), strs, defs, ens, fieldPayloadParams(splitPat(patBind(pat)), payloadTys(ens, patCore(pat)), payloadNames(ens, patCore(pat)), prefix, i, 0, ens, ps), loc) else emitExpr(rewriteNested(fieldBind(patBind(pat)), bindStem(prefix, i, bindIsInt(pat, ens)), fieldTy(ens, patCore(pat)), ens, parseGuard(g)), Str.concat(prefix, Str.concat("_g0_", Str.fromInt(i))), strs, defs, ens, nestBps(fieldBind(patBind(pat)), bindStem(prefix, i, bindIsInt(pat, ens)), fieldTy(ens, patCore(pat)), ens, ps), loc) def emitTailGuard(gs: Slot, code: String, i: Int, last: Bool): String = gs match { @@ -4777,7 +4819,7 @@ def emitOkTailSlot2(pat: String, body: Expr, strs: List[String], defs: Ftab, ens if (isOrPat(pat)) emitOkTailSlot2(orLeft(pat), body, strs, defs, ens, ps, loc, i, tys) else if (isConsPat(pat)) emitOkOwned(emitOkConsNest(consTail(pat), "body", i, tys, emitTailLeafTyped(rewriteCons(pat, body, "body", i, slotInt(tys, 0)), strs, defs, ens, ps, consPayloadParams(pat, "body", i, tys, ps), loc, i)), ps) else emitOkTailSlot3(pat, body, strs, defs, ens, ps, loc, i, tys) def emitOkTailSlot3(pat: String, body: Expr, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, i: Int, tys: List[String]): Slot = - if (isTuplePat(pat)) emitTailLeafTyped(rewriteTuple(pat, body, "body", i, tys), strs, defs, ens, ps, stemBps(pat, "body", i, tys, ps), loc, i) else if (hasComma(patBind(pat)) || namedMulti(pat, ens)) emitTailLeafTyped(rewriteFields(splitPat(patBind(pat)), body, "body", i, 0, payloadTys(ens, patCore(pat)), payloadNames(ens, patCore(pat))), strs, defs, ens, ps, fieldPayloadParams(splitPat(patBind(pat)), payloadTys(ens, patCore(pat)), payloadNames(ens, patCore(pat)), "body", i, 0, ps), loc, i) else emitTailLeafBind(fieldBind(patBind(pat)), bindIsInt(pat, ens), body, strs, defs, ens, ps, patternPayloadParams(pat, "body", i, ens, ps), loc, i) + if (isTuplePat(pat)) emitTailLeafTyped(rewriteTuple(pat, body, "body", i, tys, ens), strs, defs, ens, ps, stemBps(pat, "body", i, tys, ens, ps), loc, i) else if (hasComma(patBind(pat)) || namedMulti(pat, ens)) emitTailLeafTyped(rewriteFields(splitPat(patBind(pat)), body, "body", i, 0, payloadTys(ens, patCore(pat)), payloadNames(ens, patCore(pat)), ens), strs, defs, ens, ps, fieldPayloadParams(splitPat(patBind(pat)), payloadTys(ens, patCore(pat)), payloadNames(ens, patCore(pat)), "body", i, 0, ens, ps), loc, i) else emitTailLeafBind(fieldBind(patBind(pat)), bindIsInt(pat, ens), body, strs, defs, ens, ps, patternPayloadParams(pat, "body", i, ens, ps), loc, i) def emitTailLeafTyped(body: Expr, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], bps: List[Param], loc: String, i: Int): Slot = Slot(emitSelfTailTyped(body, Str.concat("body_a0_", Str.fromInt(i)), strs, defs, ens, ps, bps, loc), "tco", false) diff --git a/examples/kernel/add.scuzz_verify b/examples/kernel/add.scuzz_verify index 1dc240d4..f27aee6f 100644 --- a/examples/kernel/add.scuzz_verify +++ b/examples/kernel/add.scuzz_verify @@ -13,6 +13,9 @@ def sumToTen(): Bool = def termDiff(t: Term): Bool = Main.termAgrees(t) +def nestedTerm(t: Term): Bool = + Main.evalNestedAdd(t) == Main.evalTerm(t) + def utf8Ops(): Bool = Main.utf8Check() == 1 diff --git a/examples/kernel/src/Main.scuzz b/examples/kernel/src/Main.scuzz index 5721258f..adcc35ea 100644 --- a/examples/kernel/src/Main.scuzz +++ b/examples/kernel/src/Main.scuzz @@ -136,6 +136,13 @@ def evalTerm(t: Term): Int = case Term.Add(a, b) => evalTerm(a) + evalTerm(b) } +def evalNestedAdd(t: Term): Int = + t match { + case Term.Add(Term.N(n), Term.N(m)) => n + m + case Term.Add(a, b) => evalTerm(a) + evalTerm(b) + case Term.N(n) => n + } + def _termLeaves(t: Term): List[Int] = t match { case Term.N(n) => [n] @@ -1153,6 +1160,7 @@ b""" _ <- IO.println(s"tco:${Str.fromInt(countdown(1000000))}") _ <- IO.println(s"tcom:${Str.fromInt(countdownMatch(1000000))}") _ <- IO.println(s"tcol:${Str.fromInt(sumAcc([1, 2, 3], 0))}") + _ <- IO.println(s"nest:${Str.fromInt(evalNestedAdd(Term.Add(Term.N(2), Term.N(3))))}") _ <- IO.println(s"diff:${if (sumTo(10, 0) == Oracle.sumTo(10)) "y" else "n"}") _ <- builderProof() _ <- utf8Proof() diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index 118179e2..355c971a 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -806,7 +806,24 @@ def dumpFail(): String = if (!tyckCore()) dumpTyckCore() else if (!tyckFlow()) "tyckFlow" else if (!tyckRes()) dumpTyckRes() else if (!tyckFail()) dumpTyckFail() else if (!tyckParse()) "tyckParse" else if (!tyckLam()) dumpTyckLam() else if (!tyckAdt()) dumpTyckAdt() else if (!tyckClosed()) dumpClosed() else if (!tyckAny()) "tyckAny" else "other" def dumpTyckRes(): String = - if (!namedIoTypes()) dumpNamedIo() else "tyckRes" + if (!nestedPatTypes()) dumpNestedPat() else if (!namedIoTypes()) dumpNamedIo() else "tyckRes" + +def dumpNestedPat(): String = + if (Check.check("""enum Inner: + case Good(v: Int) +enum Outer: + case Wrap(a: Inner, b: Int) +def right(o: Outer): Int = o match { + case Outer.Wrap(Inner.Good(n), b) => n + case _ => 0 +}""") != "[]") Str.concat("nestCtor ", Check.check("""enum Inner: + case Good(v: Int) +enum Outer: + case Wrap(a: Inner, b: Int) +def right(o: Outer): Int = o match { + case Outer.Wrap(Inner.Good(n), b) => n + case _ => 0 +}""")) else "nestedPat-other" def dumpNamedIo(): String = if (Check.check("def right(io: IO[Int]): IO[String] = io.map(n => Str.fromInt(n))") != "[]") Str.concat("ioMapOk ", Check.check("def right(io: IO[Int]): IO[String] = io.map(n => Str.fromInt(n))")) else if (!ioFunTypes()) dumpIoFun() else "namedIo-other" @@ -1034,39 +1051,39 @@ def lambdaApplyTypes(): Bool = """, "lambda parameter type is unresolved") && Check.check("@main def main: IO[Unit] =\n for {\n f = ((n: String) => n)\n _ <- IO.println(f(\"s\"))\n } yield ()\n") == "[]" && Check.check("def right(): Int = (n => n + 1)(1)") == "[]" && rejects("def wrong(): Int = (n => n + 1)(\"s\")", "arithmetic needs Int or Float") def nestedPatTypes(): Bool = - rejects("""enum Inner: + Check.check("""enum Inner: case Good(v: Int) enum Outer: case Wrap(a: Inner, b: Int) -def wrong(o: Outer): Int = o match { +def right(o: Outer): Int = o match { case Outer.Wrap(Inner.Good(n), b) => n case _ => 0 -}""", "nested patterns are not supported here") && rejects("""enum Box2: +}""") == "[]" && Check.check("""enum Box2: case P(p: (Int, Int)) -def wrong(b: Box2): Int = b match { +def right(b: Box2): Int = b match { case Box2.P((n, m)) => n + m case _ => 0 -}""", "nested patterns are not supported here") && rejects("""def wrong(p: (Result[String, Int], Int)): Int = p match { +}""") == "[]" && Check.check("""def right(p: (Result[String, Int], Int)): Int = p match { case (Result.Ok(n), m) => n + m case _ => 0 -}""", "nested patterns are not supported here") && rejects("""def wrong(p: (List[Int], Int)): Int = p match { +}""") == "[]" && Check.check("""def right(p: (List[Int], Int)): Int = p match { case (x :: _, n) => x + n case _ => 0 -}""", "nested patterns are not supported here") && rejects("""enum Opt: +}""") == "[]" && Check.check("""enum Opt: case Some(value: Int) case None -def wrong(o: Opt): Int = o match { +def right(o: Opt): Int = o match { case Opt.Some(n @ 0) => n case _ => 0 -}""", "nested patterns are not supported here") && rejects("""enum Opt: +}""") == "[]" && Check.check("""enum Opt: case Some(value: Int) case None enum W: case V(o: Opt, n: Int) -def wrong(w: W): Int = w match { +def right(w: W): Int = w match { case W.V(Opt.None, _) => 100 case _ => 0 -}""", "nested patterns are not supported here") && Check.check("""enum W: +}""") == "[]" && Check.check("""enum W: case V(n: Int, m: Int) def right(w: W): Int = w match { case W.V(1, _) => 1 @@ -1086,12 +1103,12 @@ def right(o: Opt): Int = o match { }""") == "[]" && Check.check("""def right(p: ((Int, Int), Int)): Int = p match { case ((1, _), n) => n case ((a, b), n) => a + b + n -}""") == "[]" && rejects("""enum BoxL: +}""") == "[]" && Check.check("""enum BoxL: case L(xs: List[Int]) -def wrong(b: BoxL): Int = b match { +def right(b: BoxL): Int = b match { case BoxL.L([]) => 1 case _ => 0 -}""", "nested patterns are not supported here") +}""") == "[]" def queueRefineTypes(): Bool = Check.check("""@main def main: IO[Unit] = From 445a63d2aec3219789779a3b18f69b0b046dc08b Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 16 Sep 2026 23:34:32 -0400 Subject: [PATCH 13/23] Pin a generic def's type parameters when checking its body. A parameter letter must not match a concrete return type, so `id[A](x: A): Int = x` fails while call-site instantiation still works. --- docs/gaps.md | 2 +- docs/philosophy.md | 1 + examples/compiler/src/Check.scuzz | 17 ++++++++++------- examples/tyck/src/Main.scuzz | 13 +++++++------ 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/docs/gaps.md b/docs/gaps.md index 5f7dc319..03d291f8 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -37,7 +37,7 @@ Do not add library publishing, git or registry deps, or `scuzz add`. Path deps s Resolve these gaps when they prevent ordinary language use. -1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions and payloads seen through a lambda parameter over a collection. Applying an env-bound lambda with an unresolved parameter letter to a concrete argument fails check. An unannotated lambda in argument or def-body position binds its parameter from the expected function type, so a loose lambda can no longer escape through an expected function type. Param letters (`A`/`E`) still unify elsewhere in `Type.eq`. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Tuple components and constructor fields compare String, Int, and Bool literals. Constructor, tuple, cons, as, and `[]` patterns nest in those positions. +1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions and payloads seen through a lambda parameter over a collection. Applying an env-bound lambda with an unresolved parameter letter to a concrete argument fails check. An unannotated lambda in argument or def-body position binds its parameter from the expected function type, so a loose lambda can no longer escape through an expected function type. A generic def pins its own type parameters when it checks the body against the declared return. Param letters (`A`/`E`) still unify elsewhere in `Type.eq`. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Tuple components and constructor fields compare String, Int, and Bool literals. Constructor, tuple, cons, as, and `[]` patterns nest in those positions. 2. **Compile-time performance** — `scuzz check examples/compiler` is 17 s. A cold `scuzz build examples/tyck` is 34 s. Emitted string literals intern to pinned allocations. Remaining cost: RC retain/release churn and `sz_list_concat` in string building. Coverage still parses a compiled graph that differs from live. diff --git a/docs/philosophy.md b/docs/philosophy.md index 200bc05f..1d3db71b 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -124,6 +124,7 @@ Locks (not an API catalog — run `scuzz docs language` and `scuzz docs kits`): - Interpolated strings use the same escape rules as ordinary strings. Decode escapes in literal segments once. Parse expressions inside interpolation braces as source. Live code and verification use the same rules. - Optional `package`; top-level `def` / `private def` / `import`; `@main def …: IO[Unit]` - Payload enums + `record` sugar + thin traits/`impl` (static dispatch) + monomorphized generics +- A generic def pins its own type parameters when it checks the body against the declared return. `A` does not match `Int` there. Call sites still instantiate parameters. - Record field lookup substitutes the receiver type arguments into the declared field type. The same rule applies inside callbacks. - Constructor patterns compare direct String, Int, and Bool literals before an arm runs. Named fields use their declared positions. A failed literal comparison tries the next arm. Constructor, tuple, cons, as, and `[]` patterns nest in constructor fields and tuple components. - Literal alternatives support chains of String, Int, or Bool values. Test each alternative before the arm guard. String contents can include the alternative separator. diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index 515f65da..e77151ef 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -1683,11 +1683,11 @@ def inferTupleRest(rest: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: L def checkDef(d: Fun, funs: Ftab, ens: List[En]): Out = d match { - case Fun(_, name, _, ps, ret, body, mod, off) => checkDefWhere(name, ret, body, ps, funs, ens, mod, off, checkWheres(ps, bindParams(ps, envSelfMod(mod)), funs, ens, (mod, off))) + case Fun(_, name, ts, ps, ret, body, mod, off) => checkDefWhere(name, ret, body, ps, funs, ens, ts, mod, off, checkWheres(ps, bindParams(ps, envSelfMod(mod)), funs, ens, (mod, off))) } -def checkDefWhere(name: String, ret: String, body: Expr, ps: List[Param], funs: Ftab, ens: List[En], mod: String, off: Int, wo: Out): Out = - if (hasErr(wo)) checkDefWrap(name, ret, body, wo, (mod, off), ens) else checkDefWrap(name, ret, body, inferExpected(body, ret, bindParams(ps, envSelfMod(mod)), funs, ens), (mod, off), ens) +def checkDefWhere(name: String, ret: String, body: Expr, ps: List[Param], funs: Ftab, ens: List[En], ts: List[String], mod: String, off: Int, wo: Out): Out = + if (hasErr(wo)) checkDefWrap(name, ret, body, wo, (mod, off), ens, ts) else checkDefWrap(name, ret, body, inferExpected(body, ret, bindParams(ps, envSelfMod(mod)), funs, ens), (mod, off), ens, ts) def checkWheres(ps: List[Param], env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = if (List.isEmpty(ps)) ok("Bool") else checkWhereHd(List.at(ps, 0), List.tail(ps), env, funs, ens, span) @@ -1728,11 +1728,14 @@ def checkEnCaseHd(c: EnCase, rest: List[EnCase], ens: List[En], funs: Ftab): Out def checkEnCasesAfter(o: Out, rest: List[EnCase], ens: List[En], funs: Ftab): Out = if (hasErr(o)) o else checkEnCases(rest, ens, funs) -def checkDefWrap(name: String, ret: String, body: Expr, o: Out, span: (String, Int), ens: List[En]): Out = - if (hasErr(o)) bad(Str.concat("in ", Str.concat(name, Str.concat(": ", o.err))), o.span) else checkDefGot(name, ret, body, o, span, ens) +def checkDefWrap(name: String, ret: String, body: Expr, o: Out, span: (String, Int), ens: List[En], ts: List[String]): Out = + if (hasErr(o)) bad(Str.concat("in ", Str.concat(name, Str.concat(": ", o.err))), o.span) else checkDefGot(name, ret, body, o, span, ens, ts) -def checkDefGot(name: String, ret: String, body: Expr, o: Out, span: (String, Int), ens: List[En]): Out = - if (hasErr(o)) o else if (tyEqEn(tyStr(o), ret, ens)) o else bad(Str.concat("def ", Str.concat(name, Str.concat(" body ", Str.concat(tyStr(o), Str.concat(" does not match declared ", ret))))), spanPick(Parse.spanOf(body), span)) +def checkDefGot(name: String, ret: String, body: Expr, o: Out, span: (String, Int), ens: List[En], ts: List[String]): Out = + if (hasErr(o)) o else if (tyEqParams(tyStr(o), ret, ens, ts)) o else bad(Str.concat("def ", Str.concat(name, Str.concat(" body ", Str.concat(tyStr(o), Str.concat(" does not match declared ", ret))))), spanPick(Parse.spanOf(body), span)) + +def tyEqParams(a: String, b: String, ens: List[En], ts: List[String]): Bool = + eqPinned(a, b, List.concat(ts, enNames(ens))) def spanPick(a: (String, Int), b: (String, Int)): (String, Int) = a match { diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index 355c971a..4d1fec98 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -361,11 +361,14 @@ def collectionTypes(): Bool = def userTypeArgs(): Bool = rejects("def choose[A](x: A, y: A): A = x\ndef wrong(): Int = choose(1, \"x\")", "arg type mismatch") && Check.check("""def choose[A](x: A, y: A): A = x def right(): Int = choose(1, 2)""") == "[]" && rejects("""def identity[A](x: A): A = x -def wrong(): String = identity(1)""", "does not match declared") +def wrong(): String = identity(1)""", "does not match declared") && rejects("def id[A](x: A): Int = x", "does not match declared") && rejects("def id[A](x: A): A = 1", "does not match declared") && Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") == "[]" def listFilterTypes(): Bool = Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared") && rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare") && Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") == "[]" && rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared") && Check.check("def right(): List[String] = List.takeWhile([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && Check.check("def right(): List[String] = List.dropRight([\"a\", \"b\"], 1)") == "[]" +def dumpUserTypeArgs(): String = + if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else "userTypeArgs-other" + def dumpListFilter(): String = if (!rejects("def wrong(): List[Int] = [1, \"x\"]", "list element")) "listElem" else if (!rejects("def wrong(): List[Int] = List.concat([1], [\"x\"])", "arg type mismatch")) "listConcat" else if (!rejects("def wrong(): List[String] = List.map([1], x => x + 1)", "does not match declared")) "listMap" else if (Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") != "[]") Str.concat("filterOk ", Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)")) else if (!rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared")) Str.concat("filterMis ", Check.check("def wrong(): List[Int] = List.filter([\"a\"], x => true)")) else if (!rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare")) Str.concat("filterPred ", Check.check("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)")) else if (Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") != "[]") Str.concat("takeOk ", Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)")) else if (!rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared")) Str.concat("takeMis ", Check.check("def wrong(): List[Int] = List.take([\"a\"], 1)")) else "listFilter-other" @@ -547,9 +550,7 @@ def srcFunSubstMis(): String = "def wrap[A](x: A): A => A =\n (y: A) => x\ndef wrong(): Int => String => String =\n wrap((n: Int) => \"x\")\n" def srcFunSubstId(): String = - """def id[A](x: A): Int = - x -def wrap[A](x: A): A => A = + """def wrap[A](x: A): A => A = (y: A) => x def right(): Int => Int = wrap(1) @@ -704,7 +705,7 @@ def wrong(): UserId => Int = def srcAliasId(): String = """type UserId = Int -def id[A](x: A): Int = +def id[A](x: A): A = x def right(): List[UserId] = [id(1)] @@ -844,7 +845,7 @@ def dumpTyckCore(): String = if (!sourceOffsets()) "sourceOffsets" else if (!funLookup()) "funLookup" else "tyckCore" def dumpTyckFail(): String = - if (!collectionTypes()) dumpListFilter() else if (!streamTypes()) dumpStream() else if (!flattenTypes()) dumpFlatten() else if (!foldTypes()) dumpFold() else if (!mapSetTypes()) dumpMapSet() else if (!zipTypes()) dumpZip() else if (!unfoldTypes()) dumpUnfold() else if (!listBuildTypes()) dumpListBuild() else if (!mapProjectTypes()) dumpMapProject() else if (!listRestTypes()) dumpListRest() else if (!streamRestTypes()) dumpStreamRest() else if (!listStructTypes()) dumpListStruct() else if (!listToMapTypes()) dumpListToMap() else if (!listSpanTypes()) dumpListSpan() else if (!listSliceTypes()) dumpListSlice() else if (!setUnionTypes()) dumpSetUnion() else if (!streamChangesTypes()) dumpStreamChanges() else if (!listFindTypes()) dumpListFind() else if (!mapExistsTypes()) dumpMapExists() else if (!listSumTypes()) dumpListSum() else if (!jsonListTypes()) dumpJsonList() else if (!editorListTypes()) dumpEditorList() else if (!propertyForceTypes()) dumpPropertyForce() else if (!namedIoTypes()) dumpNamedIo() else if (!kitLookup()) "kitLookup" else if (!ioCombinators()) dumpIoComb() else "tyckFail" + if (!collectionTypes()) dumpListFilter() else if (!userTypeArgs()) dumpUserTypeArgs() else if (!streamTypes()) dumpStream() else if (!flattenTypes()) dumpFlatten() else if (!foldTypes()) dumpFold() else if (!mapSetTypes()) dumpMapSet() else if (!zipTypes()) dumpZip() else if (!unfoldTypes()) dumpUnfold() else if (!listBuildTypes()) dumpListBuild() else if (!mapProjectTypes()) dumpMapProject() else if (!listRestTypes()) dumpListRest() else if (!streamRestTypes()) dumpStreamRest() else if (!listStructTypes()) dumpListStruct() else if (!listToMapTypes()) dumpListToMap() else if (!listSpanTypes()) dumpListSpan() else if (!listSliceTypes()) dumpListSlice() else if (!setUnionTypes()) dumpSetUnion() else if (!streamChangesTypes()) dumpStreamChanges() else if (!listFindTypes()) dumpListFind() else if (!mapExistsTypes()) dumpMapExists() else if (!listSumTypes()) dumpListSum() else if (!jsonListTypes()) dumpJsonList() else if (!editorListTypes()) dumpEditorList() else if (!propertyForceTypes()) dumpPropertyForce() else if (!namedIoTypes()) dumpNamedIo() else if (!kitLookup()) "kitLookup" else if (!ioCombinators()) dumpIoComb() else "tyckFail" def dumpIoComb(): String = if (Check.check("def right(): IO[String] = IO.timeout(50, IO.pure(\"x\"))") != "[]") Str.concat("timeoutOk ", Check.check("def right(): IO[String] = IO.timeout(50, IO.pure(\"x\"))")) else if (!rejects("def wrong(): IO[Int] = IO.timeout(50, IO.pure(\"x\"))", "does not match declared")) Str.concat("timeoutMis ", Check.check("def wrong(): IO[Int] = IO.timeout(50, IO.pure(\"x\"))")) else if (Check.check("def right(): IO[Int] = IO.race(IO.pure(1), IO.pure(2))") != "[]") Str.concat("raceOk ", Check.check("def right(): IO[Int] = IO.race(IO.pure(1), IO.pure(2))")) else if (!rejects("def wrong(): IO[Int] = IO.race(IO.pure(1), IO.pure(\"x\"))", "arg type mismatch")) Str.concat("raceMis ", Check.check("def wrong(): IO[Int] = IO.race(IO.pure(1), IO.pure(\"x\"))")) else if (Check.check("def right(): IO[(Int, String)] = IO.both(IO.pure(1), IO.pure(\"x\"))") != "[]") Str.concat("bothOk ", Check.check("def right(): IO[(Int, String)] = IO.both(IO.pure(1), IO.pure(\"x\"))")) else if (!rejects("def wrong(): IO[String] = IO.both(IO.pure(1), IO.pure(\"x\"))", "does not match declared")) Str.concat("bothMis ", Check.check("def wrong(): IO[String] = IO.both(IO.pure(1), IO.pure(\"x\"))")) else if (Check.check("def right(): IO[String] = IO.ensure(IO.pure(\"x\"), IO.pure(()))") != "[]") Str.concat("ensureOk ", Check.check("def right(): IO[String] = IO.ensure(IO.pure(\"x\"), IO.pure(()))")) else if (Check.check("def right(): IO[List[String]] = IO.foreach([\"a\"], x => IO.pure(Str.concat(x, \"!\")))") != "[]") Str.concat("foreachOk ", Check.check("def right(): IO[List[String]] = IO.foreach([\"a\"], x => IO.pure(Str.concat(x, \"!\")))")) else if (!rejects("def wrong(): IO[List[Int]] = IO.foreach([\"a\"], x => IO.pure(Str.concat(x, \"!\")))", "does not match declared")) Str.concat("foreachMis ", Check.check("def wrong(): IO[List[Int]] = IO.foreach([\"a\"], x => IO.pure(Str.concat(x, \"!\")))")) else if (Check.check("def right(): IO[Fiber[String]] = Fiber.fork(IO.pure(\"x\"))") != "[]") Str.concat("forkOk ", Check.check("def right(): IO[Fiber[String]] = Fiber.fork(IO.pure(\"x\"))")) else if (!rejects("def wrong(): IO[Fiber[Int]] = Fiber.fork(IO.pure(\"x\"))", "does not match declared")) Str.concat("forkMis ", Check.check("def wrong(): IO[Fiber[Int]] = Fiber.fork(IO.pure(\"x\"))")) else if (!bothUnpack()) Str.concat("bothUnpack ", Check.check("def wrong(): IO[Unit] =\n for {\n (_, n) <- IO.both(IO.pure(1), IO.pure(\"x\"))\n _ <- IO.println(Str.fromInt(n))\n } yield ()\n")) else if (!fiberJoin()) Str.concat("fiberJoin ", Check.check("def wrong(): IO[Unit] =\n for {\n f <- Fiber.fork(IO.pure(\"x\"))\n n <- Fiber.join(f)\n _ <- IO.println(Str.fromInt(n))\n } yield ()\n")) else if (!queueHandleTypes()) dumpQueueHandle() else dumpRef() From 499fb4c0b61d6ed54ecc1787a9da6af8cbda5472 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 17 Sep 2026 00:46:24 -0400 Subject: [PATCH 14/23] Pin generic type parameters for every check inside a def body. IO.println and kit calls treated A as String; pin the def's own parameters so those mismatches fail while IO.pure(x) still instantiates. --- docs/gaps.md | 2 +- docs/philosophy.md | 2 +- examples/compiler/src/Check.scuzz | 27 ++++++++++++++++++++++----- examples/tyck/src/Main.scuzz | 6 +++--- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/docs/gaps.md b/docs/gaps.md index 03d291f8..79df5a6b 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -37,7 +37,7 @@ Do not add library publishing, git or registry deps, or `scuzz add`. Path deps s Resolve these gaps when they prevent ordinary language use. -1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions and payloads seen through a lambda parameter over a collection. Applying an env-bound lambda with an unresolved parameter letter to a concrete argument fails check. An unannotated lambda in argument or def-body position binds its parameter from the expected function type, so a loose lambda can no longer escape through an expected function type. A generic def pins its own type parameters when it checks the body against the declared return. Param letters (`A`/`E`) still unify elsewhere in `Type.eq`. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Tuple components and constructor fields compare String, Int, and Bool literals. Constructor, tuple, cons, as, and `[]` patterns nest in those positions. +1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions and payloads seen through a lambda parameter over a collection. Applying an env-bound lambda with an unresolved parameter letter to a concrete argument fails check. An unannotated lambda in argument or def-body position binds its parameter from the expected function type, so a loose lambda can no longer escape through an expected function type. A generic def pins its own type parameters for every check in its body. Param letters (`A`/`E`) still unify in `Type.eq` outside those pins. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Tuple components and constructor fields compare String, Int, and Bool literals. Constructor, tuple, cons, as, and `[]` patterns nest in those positions. 2. **Compile-time performance** — `scuzz check examples/compiler` is 17 s. A cold `scuzz build examples/tyck` is 34 s. Emitted string literals intern to pinned allocations. Remaining cost: RC retain/release churn and `sz_list_concat` in string building. Coverage still parses a compiled graph that differs from live. diff --git a/docs/philosophy.md b/docs/philosophy.md index 1d3db71b..66600bae 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -124,7 +124,7 @@ Locks (not an API catalog — run `scuzz docs language` and `scuzz docs kits`): - Interpolated strings use the same escape rules as ordinary strings. Decode escapes in literal segments once. Parse expressions inside interpolation braces as source. Live code and verification use the same rules. - Optional `package`; top-level `def` / `private def` / `import`; `@main def …: IO[Unit]` - Payload enums + `record` sugar + thin traits/`impl` (static dispatch) + monomorphized generics -- A generic def pins its own type parameters when it checks the body against the declared return. `A` does not match `Int` there. Call sites still instantiate parameters. +- A generic def pins its own type parameters for every check in its body. `A` does not match `Int` or `String` there. Call sites still instantiate parameters. - Record field lookup substitutes the receiver type arguments into the declared field type. The same rule applies inside callbacks. - Constructor patterns compare direct String, Int, and Bool literals before an arm runs. Named fields use their declared positions. A failed literal comparison tries the next arm. Constructor, tuple, cons, as, and `[]` patterns nest in constructor fields and tuple components. - Literal alternatives support chains of String, Int, or Bool values. Test each alternative before the arm guard. String contents can include the alternative separator. diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index e77151ef..cc7e6502 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -329,11 +329,19 @@ def enNames(ens: List[En]): List[String] = if (List.isEmpty(ens)) noStr() else enNamesHd(List.at(ens, 0), List.tail(ens)) def enNamesHd(h: En, rest: List[En]): List[String] = - h.name :: enNames(rest) + h match { + case En(_, name, ts, _) => if (name == "#pin") List.concat(ts, enNames(rest)) else name :: enNames(rest) + } def tyEqEn(a: String, b: String, ens: List[En]): Bool = eqPinned(a, b, enNames(ens)) +def pinEns(ens: List[En], ts: List[String]): List[En] = + if (List.isEmpty(ts)) ens else En(false, "#pin", ts, noCase()) :: ens + +def noCase(): List[EnCase] = + [] + def preferEn(a: String, b: String, ens: List[En]): String = preferPinned(a, b, enNames(ens)) @@ -343,6 +351,12 @@ def prefer(a: String, b: String): String = def isLoose(t: String): Bool = isTyParam(t) +def isLooseEn(t: String, ens: List[En]): Bool = + isTyParam(t) && !nameIn(enNames(ens), t) + +def nameIn(ns: List[String], n: String): Bool = + if (List.isEmpty(ns)) false else if (List.at(ns, 0) == n) true else nameIn(List.tail(ns), n) + def funArgOf(t: String): String = Type.funArgStr(t) @@ -676,10 +690,10 @@ def inferCall(f: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, resolveCall(f, args, env, funs, ens, span) def inferPrint(inner: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = - inferPrint2(infer(inner, env, funs, ens), env, inner, e) + inferPrint2(infer(inner, env, funs, ens), env, inner, e, ens) -def inferPrint2(o: Out, env: List[(String, Ty)], inner: Expr, e: Expr): Out = - if (hasErr(o)) o else if (tyStr(o) == "String" || isLoose(tyStr(o))) ok("IO[Unit]") else bad(Str.concat("expected String, got ", tyStr(o)), spanPick(exprSpan(env, inner), exprSpan(env, e))) +def inferPrint2(o: Out, env: List[(String, Ty)], inner: Expr, e: Expr, ens: List[En]): Out = + if (hasErr(o)) o else if (tyStr(o) == "String" || isLooseEn(tyStr(o), ens)) ok("IO[Unit]") else bad(Str.concat("expected String, got ", tyStr(o)), spanPick(exprSpan(env, inner), exprSpan(env, e))) def inferBin(op: String, l: Expr, r: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = inferBin2(op, infer(l, env, funs, ens), r, env, funs, ens, e) @@ -1683,9 +1697,12 @@ def inferTupleRest(rest: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: L def checkDef(d: Fun, funs: Ftab, ens: List[En]): Out = d match { - case Fun(_, name, ts, ps, ret, body, mod, off) => checkDefWhere(name, ret, body, ps, funs, ens, ts, mod, off, checkWheres(ps, bindParams(ps, envSelfMod(mod)), funs, ens, (mod, off))) + case Fun(_, name, ts, ps, ret, body, mod, off) => checkDefGo(name, ret, body, ps, funs, pinEns(ens, ts), ts, mod, off) } +def checkDefGo(name: String, ret: String, body: Expr, ps: List[Param], funs: Ftab, ens: List[En], ts: List[String], mod: String, off: Int): Out = + checkDefWhere(name, ret, body, ps, funs, ens, ts, mod, off, checkWheres(ps, bindParams(ps, envSelfMod(mod)), funs, ens, (mod, off))) + def checkDefWhere(name: String, ret: String, body: Expr, ps: List[Param], funs: Ftab, ens: List[En], ts: List[String], mod: String, off: Int, wo: Out): Out = if (hasErr(wo)) checkDefWrap(name, ret, body, wo, (mod, off), ens, ts) else checkDefWrap(name, ret, body, inferExpected(body, ret, bindParams(ps, envSelfMod(mod)), funs, ens), (mod, off), ens, ts) diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index 4d1fec98..1fa69b35 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -359,15 +359,15 @@ def collectionTypes(): Bool = rejects("def wrong(): List[Int] = [1, \"x\"]", "list element") && rejects("def wrong(): List[Int] = [1, missing]", "unbound") && rejects("def wrong(): List[Int] = List.concat([1], [\"x\"])", "arg type mismatch") && rejects("def wrong(): List[String] = List.map([1], x => x + 1)", "does not match declared") && listFilterTypes() def userTypeArgs(): Bool = - rejects("def choose[A](x: A, y: A): A = x\ndef wrong(): Int = choose(1, \"x\")", "arg type mismatch") && Check.check("""def choose[A](x: A, y: A): A = x + !Type.eqPinned("A", "String", "A" :: []) && rejects("def choose[A](x: A, y: A): A = x\ndef wrong(): Int = choose(1, \"x\")", "arg type mismatch") && Check.check("""def choose[A](x: A, y: A): A = x def right(): Int = choose(1, 2)""") == "[]" && rejects("""def identity[A](x: A): A = x -def wrong(): String = identity(1)""", "does not match declared") && rejects("def id[A](x: A): Int = x", "does not match declared") && rejects("def id[A](x: A): A = 1", "does not match declared") && Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") == "[]" +def wrong(): String = identity(1)""", "does not match declared") && rejects("def id[A](x: A): Int = x", "does not match declared") && rejects("def id[A](x: A): A = 1", "does not match declared") && Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") == "[]" && Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") == "[]" && rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got") && rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch") def listFilterTypes(): Bool = Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared") && rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare") && Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") == "[]" && rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared") && Check.check("def right(): List[String] = List.takeWhile([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && Check.check("def right(): List[String] = List.dropRight([\"a\", \"b\"], 1)") == "[]" def dumpUserTypeArgs(): String = - if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else "userTypeArgs-other" + if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else "userTypeArgs-other" def dumpListFilter(): String = if (!rejects("def wrong(): List[Int] = [1, \"x\"]", "list element")) "listElem" else if (!rejects("def wrong(): List[Int] = List.concat([1], [\"x\"])", "arg type mismatch")) "listConcat" else if (!rejects("def wrong(): List[String] = List.map([1], x => x + 1)", "does not match declared")) "listMap" else if (Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") != "[]") Str.concat("filterOk ", Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)")) else if (!rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared")) Str.concat("filterMis ", Check.check("def wrong(): List[Int] = List.filter([\"a\"], x => true)")) else if (!rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare")) Str.concat("filterPred ", Check.check("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)")) else if (Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") != "[]") Str.concat("takeOk ", Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)")) else if (!rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared")) Str.concat("takeMis ", Check.check("def wrong(): List[Int] = List.take([\"a\"], 1)")) else "listFilter-other" From 027ac8936ce374b8dcf39442b7dd7ba189c3d3b2 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 17 Sep 2026 01:32:50 -0400 Subject: [PATCH 15/23] Reject arithmetic on a generic parameter inside its def. Pinned A was still treated as Int in + , compare, and unary minus, so x + 1 typechecked. --- examples/compiler/src/Check.scuzz | 40 +++++++++++++++---------------- examples/tyck/src/Main.scuzz | 4 ++-- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index cc7e6502..60ec7b01 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -705,43 +705,43 @@ def inferBin3(op: String, lo: Out, ro: Out, env: List[(String, Ty)], ens: List[E if (hasErr(ro)) ro else inferBin4(op, tyStr(lo), tyStr(ro), exprSpan(env, e), ens) def inferBin4(op: String, lt: String, rt: String, span: (String, Int), ens: List[En]): Out = - if (isArith(op)) inferArith(lt, rt, span) else if (isEq(op)) inferEq(lt, rt, span, ens) else if (isOrd(op)) inferOrd(lt, rt, span) else if (isLogic(op)) inferLogic(lt, rt, span) else if (isBit(op)) inferBit(lt, rt, span) else if (op == "::") ok(Str.concat("List[", Str.concat(lt, "]"))) else ok(lt) + if (isArith(op)) inferArith(lt, rt, span, ens) else if (isEq(op)) inferEq(lt, rt, span, ens) else if (isOrd(op)) inferOrd(lt, rt, span, ens) else if (isLogic(op)) inferLogic(lt, rt, span, ens) else if (isBit(op)) inferBit(lt, rt, span) else if (op == "::") ok(Str.concat("List[", Str.concat(lt, "]"))) else ok(lt) -def inferArith(lt: String, rt: String, span: (String, Int)): Out = - if (numTy(lt) && numTy(rt)) ok(if (lt == "Float" || rt == "Float") "Float" else "Int") else bad(Str.concat("arithmetic needs Int or Float, got ", Str.concat(lt, Str.concat(" and ", rt))), span) +def inferArith(lt: String, rt: String, span: (String, Int), ens: List[En]): Out = + if (numTy(lt, ens) && numTy(rt, ens)) ok(if (lt == "Float" || rt == "Float") "Float" else "Int") else bad(Str.concat("arithmetic needs Int or Float, got ", Str.concat(lt, Str.concat(" and ", rt))), span) -def numTy(t: String): Bool = - t == "Int" || t == "Float" || isLoose(t) +def numTy(t: String, ens: List[En]): Bool = + t == "Int" || t == "Float" || isLooseEn(t, ens) def inferEq(lt: String, rt: String, span: (String, Int), ens: List[En]): Out = if (tyEqEn(lt, rt, ens)) ok("Bool") else bad(Str.concat("comparison type mismatch ", Str.concat(lt, Str.concat(" vs ", rt))), span) -def inferOrd(lt: String, rt: String, span: (String, Int)): Out = - if (numTy(lt) && numTy(rt)) ok("Bool") else bad("ordered compare needs Int or Float", span) +def inferOrd(lt: String, rt: String, span: (String, Int), ens: List[En]): Out = + if (numTy(lt, ens) && numTy(rt, ens)) ok("Bool") else bad("ordered compare needs Int or Float", span) -def inferLogic(lt: String, rt: String, span: (String, Int)): Out = - if (logicTy(lt) && logicTy(rt)) ok("Bool") else bad(Str.concat("logic needs Bool, got ", Str.concat(lt, Str.concat(" and ", rt))), span) +def inferLogic(lt: String, rt: String, span: (String, Int), ens: List[En]): Out = + if (logicTy(lt, ens) && logicTy(rt, ens)) ok("Bool") else bad(Str.concat("logic needs Bool, got ", Str.concat(lt, Str.concat(" and ", rt))), span) -def logicTy(t: String): Bool = - t == "Bool" || isLoose(t) +def logicTy(t: String, ens: List[En]): Bool = + t == "Bool" || isLooseEn(t, ens) def inferBit(lt: String, rt: String, span: (String, Int)): Out = if (lt == "Int" && rt == "Int") ok("Int") else bad(Str.concat("bitwise needs Int, got ", Str.concat(lt, Str.concat(" and ", rt))), span) def inferUn(op: String, inner: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = - inferUn2(op, infer(inner, env, funs, ens), env, e) + inferUn2(op, infer(inner, env, funs, ens), env, ens, e) -def inferUn2(op: String, o: Out, env: List[(String, Ty)], e: Expr): Out = - if (hasErr(o)) o else if (op == "!") inferUnBool(o, env, e) else if (op == "-") inferUnNum(o, env, e) else inferUnInt(o, env, e) +def inferUn2(op: String, o: Out, env: List[(String, Ty)], ens: List[En], e: Expr): Out = + if (hasErr(o)) o else if (op == "!") inferUnBool(o, env, ens, e) else if (op == "-") inferUnNum(o, env, ens, e) else inferUnInt(o, env, ens, e) -def inferUnNum(o: Out, env: List[(String, Ty)], e: Expr): Out = - if (tyStr(o) == "Int" || tyStr(o) == "Float" || isLoose(tyStr(o))) o else bad(Str.concat("expected Int or Float, got ", tyStr(o)), exprSpan(env, e)) +def inferUnNum(o: Out, env: List[(String, Ty)], ens: List[En], e: Expr): Out = + if (tyStr(o) == "Int" || tyStr(o) == "Float" || isLooseEn(tyStr(o), ens)) o else bad(Str.concat("expected Int or Float, got ", tyStr(o)), exprSpan(env, e)) -def inferUnBool(o: Out, env: List[(String, Ty)], e: Expr): Out = - if (tyStr(o) == "Bool" || isLoose(tyStr(o))) ok("Bool") else bad(Str.concat("expected Bool, got ", tyStr(o)), exprSpan(env, e)) +def inferUnBool(o: Out, env: List[(String, Ty)], ens: List[En], e: Expr): Out = + if (tyStr(o) == "Bool" || isLooseEn(tyStr(o), ens)) ok("Bool") else bad(Str.concat("expected Bool, got ", tyStr(o)), exprSpan(env, e)) -def inferUnInt(o: Out, env: List[(String, Ty)], e: Expr): Out = - if (tyStr(o) == "Int" || isLoose(tyStr(o))) ok("Int") else bad(Str.concat("expected Int, got ", tyStr(o)), exprSpan(env, e)) +def inferUnInt(o: Out, env: List[(String, Ty)], ens: List[En], e: Expr): Out = + if (tyStr(o) == "Int" || isLooseEn(tyStr(o), ens)) ok("Int") else bad(Str.concat("expected Int, got ", tyStr(o)), exprSpan(env, e)) def inferIf(c: Expr, t: Expr, el: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = inferIf2(infer(c, env, funs, ens), t, el, env, funs, ens, e) diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index 1fa69b35..be4e5d85 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -361,13 +361,13 @@ def collectionTypes(): Bool = def userTypeArgs(): Bool = !Type.eqPinned("A", "String", "A" :: []) && rejects("def choose[A](x: A, y: A): A = x\ndef wrong(): Int = choose(1, \"x\")", "arg type mismatch") && Check.check("""def choose[A](x: A, y: A): A = x def right(): Int = choose(1, 2)""") == "[]" && rejects("""def identity[A](x: A): A = x -def wrong(): String = identity(1)""", "does not match declared") && rejects("def id[A](x: A): Int = x", "does not match declared") && rejects("def id[A](x: A): A = 1", "does not match declared") && Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") == "[]" && Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") == "[]" && rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got") && rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch") +def wrong(): String = identity(1)""", "does not match declared") && rejects("def id[A](x: A): Int = x", "does not match declared") && rejects("def id[A](x: A): A = 1", "does not match declared") && Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") == "[]" && Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") == "[]" && rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got") && rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch") && rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs") && rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare") && rejects("def wrong[A](x: A): Int = -x", "expected Int or Float") && Check.check("def right(): Int = (n => n + 1)(1)") == "[]" def listFilterTypes(): Bool = Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared") && rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare") && Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") == "[]" && rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared") && Check.check("def right(): List[String] = List.takeWhile([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && Check.check("def right(): List[String] = List.dropRight([\"a\", \"b\"], 1)") == "[]" def dumpUserTypeArgs(): String = - if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else "userTypeArgs-other" + if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else "userTypeArgs-other" def dumpListFilter(): String = if (!rejects("def wrong(): List[Int] = [1, \"x\"]", "list element")) "listElem" else if (!rejects("def wrong(): List[Int] = List.concat([1], [\"x\"])", "arg type mismatch")) "listConcat" else if (!rejects("def wrong(): List[String] = List.map([1], x => x + 1)", "does not match declared")) "listMap" else if (Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") != "[]") Str.concat("filterOk ", Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)")) else if (!rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared")) Str.concat("filterMis ", Check.check("def wrong(): List[Int] = List.filter([\"a\"], x => true)")) else if (!rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare")) Str.concat("filterPred ", Check.check("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)")) else if (Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") != "[]") Str.concat("takeOk ", Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)")) else if (!rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared")) Str.concat("takeMis ", Check.check("def wrong(): List[Int] = List.take([\"a\"], 1)")) else "listFilter-other" From 27662f0f60bfdf28f5f4cc7e016c92ee03caeff2 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 17 Sep 2026 02:35:42 -0400 Subject: [PATCH 16/23] Reject flatMap, apply, and Resource.make on a generic parameter. Pinned A was still treated as IO or as a function, so x.flatMap and x.apply typechecked. --- examples/compiler/src/Check.scuzz | 36 +++++++++++++++---------------- examples/tyck/src/Main.scuzz | 7 ++++-- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index 60ec7b01..758af40c 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -509,8 +509,8 @@ def resolveResource2(f: String, ao: Out, lam: Expr, env: List[(String, Ty)], fun def isRes(t: String): Bool = Type.isResStr(t) -def resTy(a: String): String = - if (a == "" || isLoose(a)) "Resource" else Str.concat("Resource[", Str.concat(a, "]")) +def resTy(a: String, ens: List[En]): String = + if (a == "" || isLooseEn(a, ens)) "Resource" else Str.concat("Resource[", Str.concat(a, "]")) def resPayload(t: String): String = if (isRes(t) && brackAt(t, 0) >= 0) elemOf(t) else "A" @@ -519,10 +519,10 @@ def bindRes(p: String, ty: String, env: List[(String, Ty)]): List[(String, Ty)] envBind(p, ty, env) def resolveResMake(ao: Out, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = - if (isIo(tyStr(ao)) || isLoose(tyStr(ao))) resolveResLam(tyStr(ao), ioPayload(tyStr(ao)), lam, env, funs, ens, span, resTy(ioPayload(tyStr(ao)))) else bad(Str.concat("Resource.make needs IO, got ", tyStr(ao)), span) + if (isIo(tyStr(ao)) || isLooseEn(tyStr(ao), ens)) resolveResLam(tyStr(ao), ioPayload(tyStr(ao)), lam, env, funs, ens, span, resTy(ioPayload(tyStr(ao)), ens)) else bad(Str.concat("Resource.make needs IO, got ", tyStr(ao)), span) def resolveResUse(ao: Out, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = - if (isRes(tyStr(ao)) || isLoose(tyStr(ao))) resolveResLam(tyStr(ao), resPayload(tyStr(ao)), lam, env, funs, ens, span, "") else bad(Str.concat("Resource.use needs Resource, got ", tyStr(ao)), span) + if (isRes(tyStr(ao)) || isLooseEn(tyStr(ao), ens)) resolveResLam(tyStr(ao), resPayload(tyStr(ao)), lam, env, funs, ens, span, "") else bad(Str.concat("Resource.use needs Resource, got ", tyStr(ao)), span) def resolveResLam(recv: String, bind: String, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int), makeRet: String): Out = lam match { @@ -532,13 +532,13 @@ def resolveResLam(recv: String, bind: String, lam: Expr, env: List[(String, Ty)] } def resolveResAnn(a: Out, recv: String, p: String, body: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int), makeRet: String): Out = - if (hasErr(a)) a else resolveResBody(recv, infer(body, bindRes(p, bindOfAnn(a), env), funs, ens), span, makeRet) + if (hasErr(a)) a else resolveResBody(recv, infer(body, bindRes(p, bindOfAnn(a), env), funs, ens), span, makeRet, ens) def bindOfAnn(a: Out): String = tyStr(a) -def resolveResBody(recv: String, body: Out, span: (String, Int), makeRet: String): Out = - if (hasErr(body)) body else if (isIo(tyStr(body)) || isLoose(tyStr(body))) ok(if (makeRet != "") makeRet else if (isIo(tyStr(body))) tyStr(body) else "IO[A]") else bad(Str.concat(if (makeRet == "") "Resource.use" else "Resource.make", Str.concat(" needs IO, got ", Str.concat(recv, Str.concat(" and ", tyStr(body))))), span) +def resolveResBody(recv: String, body: Out, span: (String, Int), makeRet: String, ens: List[En]): Out = + if (hasErr(body)) body else if (isIo(tyStr(body)) || isLooseEn(tyStr(body), ens)) ok(if (makeRet != "") makeRet else if (isIo(tyStr(body))) tyStr(body) else "IO[A]") else bad(Str.concat(if (makeRet == "") "Resource.use" else "Resource.make", Str.concat(" needs IO, got ", Str.concat(recv, Str.concat(" and ", tyStr(body))))), span) def netReqTy(): String = "(String, String, Map[String, String], String)" @@ -1511,7 +1511,7 @@ def inferApplyLam3(lo: Out, argTy: String, env: List[(String, Ty)], ens: List[En if (hasErr(lo)) lo else if (tyEqEn(argTy, Type.show(Type.funArg(lo.ty)), ens)) okTy(Type.funRet(lo.ty)) else argMismatch("apply", Type.show(Type.funArg(lo.ty)), argTy, asp) def inferApply2(o: Out, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = - if (hasErr(o)) o else if (Type.isFun(o.ty) || isLoose(tyStr(o))) inferFunApply(o.ty, args, env, funs, ens, exprSpan(env, e)) else bad(Str.concat("apply needs a function, got ", tyStr(o)), exprSpan(env, e)) + if (hasErr(o)) o else if (Type.isFun(o.ty) || isLooseEn(tyStr(o), ens)) inferFunApply(o.ty, args, env, funs, ens, exprSpan(env, e)) else bad(Str.concat("apply needs a function, got ", tyStr(o)), exprSpan(env, e)) def inferMethod2(recv: Expr, name: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = recv match { @@ -1597,11 +1597,11 @@ def inferFlatMap2(o: Out, args: List[Expr], env: List[(String, Ty)], funs: Ftab, def inferFlatMapLam(recv: Out, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = lam match { case Expr.ELam(p, ty, body) => inferFlatMapAnn(annOk(ty, ioPayload(tyStr(recv)), exprSpan(env, e)), recv, p, body, env, funs, ens, e) - case _ => inferFlatMap3(recv, infer(lam, env, funs, ens), env, e) + case _ => inferFlatMap3(recv, infer(lam, env, funs, ens), env, ens, e) } def inferFlatMapAnn(a: Out, recv: Out, p: String, body: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = - if (hasErr(a)) a else inferFlatMap3(recv, infer(body, bindIo(p, tyStr(recv), env), funs, ens), env, e) + if (hasErr(a)) a else inferFlatMap3(recv, infer(body, bindIo(p, tyStr(recv), env), funs, ens), env, ens, e) def bindIo(p: String, ioTy: String, env: List[(String, Ty)]): List[(String, Ty)] = envBind(p, ioPayload(ioTy), env) @@ -1609,11 +1609,11 @@ def bindIo(p: String, ioTy: String, env: List[(String, Ty)]): List[(String, Ty)] def ioPayload(t: String): String = if (isIo(t)) ioOk(t) else "A" -def inferFlatMap3(recv: Out, body: Out, env: List[(String, Ty)], e: Expr): Out = - if (hasErr(body)) body else inferFlatMap4(recv, body, env, e) +def inferFlatMap3(recv: Out, body: Out, env: List[(String, Ty)], ens: List[En], e: Expr): Out = + if (hasErr(body)) body else inferFlatMap4(recv, body, env, ens, e) -def inferFlatMap4(recv: Out, body: Out, env: List[(String, Ty)], e: Expr): Out = - if (isIo(tyStr(recv)) && isIo(tyStr(body))) inferFlatMapErr(recv, body, env, e) else if (isIo(tyStr(recv)) && isLoose(tyStr(body))) okTy(recv.ty) else if (isLoose(tyStr(recv))) ok(if (isIo(tyStr(body))) tyStr(body) else "IO[A]") else bad(Str.concat("flatMap needs IO, got ", Str.concat(tyStr(recv), Str.concat(" and ", tyStr(body)))), exprSpan(env, e)) +def inferFlatMap4(recv: Out, body: Out, env: List[(String, Ty)], ens: List[En], e: Expr): Out = + if (isIo(tyStr(recv)) && isIo(tyStr(body))) inferFlatMapErr(recv, body, env, e) else if (isIo(tyStr(recv)) && isLooseEn(tyStr(body), ens)) okTy(recv.ty) else if (isLooseEn(tyStr(recv), ens)) ok(if (isIo(tyStr(body))) tyStr(body) else "IO[A]") else bad(Str.concat("flatMap needs IO, got ", Str.concat(tyStr(recv), Str.concat(" and ", tyStr(body)))), exprSpan(env, e)) def inferFlatMapErr(recv: Out, body: Out, env: List[(String, Ty)], e: Expr): Out = inferFlatMapErr2(unifyErr(ioErr(tyStr(recv)), ioErr(tyStr(body)), exprSpan(env, e)), recv, body) @@ -1630,17 +1630,17 @@ def inferHandle2(o: Out, args: List[Expr], env: List[(String, Ty)], funs: Ftab, def inferHandleLam(recv: Out, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = lam match { case Expr.ELam(p, ty, body) => inferHandleAnn(annOk(ty, ioErr(tyStr(recv)), exprSpan(env, e)), recv, p, body, env, funs, ens, e) - case _ => inferHandle3(recv, infer(lam, env, funs, ens), env, e) + case _ => inferHandle3(recv, infer(lam, env, funs, ens), env, ens, e) } def inferHandleAnn(a: Out, recv: Out, p: String, body: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = - if (hasErr(a)) a else inferHandle3(recv, infer(body, bindHandle(p, tyStr(recv), env), funs, ens), env, e) + if (hasErr(a)) a else inferHandle3(recv, infer(body, bindHandle(p, tyStr(recv), env), funs, ens), env, ens, e) def bindHandle(p: String, ioTy: String, env: List[(String, Ty)]): List[(String, Ty)] = envBind(p, ioErr(ioTy), env) -def inferHandle3(recv: Out, body: Out, env: List[(String, Ty)], e: Expr): Out = - if (hasErr(body)) body else if (isIo(tyStr(recv)) && isIo(tyStr(body))) inferHandleOk(recv, body, env, e) else if (isIo(tyStr(recv)) && isLoose(tyStr(body))) okTy(recv.ty) else bad(Str.concat("handleErrorWith needs IO, got ", Str.concat(tyStr(recv), Str.concat(" and ", tyStr(body)))), exprSpan(env, e)) +def inferHandle3(recv: Out, body: Out, env: List[(String, Ty)], ens: List[En], e: Expr): Out = + if (hasErr(body)) body else if (isIo(tyStr(recv)) && isIo(tyStr(body))) inferHandleOk(recv, body, env, e) else if (isIo(tyStr(recv)) && isLooseEn(tyStr(body), ens)) okTy(recv.ty) else bad(Str.concat("handleErrorWith needs IO, got ", Str.concat(tyStr(recv), Str.concat(" and ", tyStr(body)))), exprSpan(env, e)) def inferHandleOk(recv: Out, body: Out, env: List[(String, Ty)], e: Expr): Out = if (tyEq(ioOk(tyStr(recv)), ioOk(tyStr(body)))) ok(ioTy(ioErr(tyStr(body)), prefer(ioOk(tyStr(recv)), ioOk(tyStr(body))))) else bad(Str.concat("handleErrorWith type mismatch: expected ", Str.concat(ioOk(tyStr(recv)), Str.concat(", got ", ioOk(tyStr(body))))), exprSpan(env, e)) diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index be4e5d85..912be72a 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -361,13 +361,16 @@ def collectionTypes(): Bool = def userTypeArgs(): Bool = !Type.eqPinned("A", "String", "A" :: []) && rejects("def choose[A](x: A, y: A): A = x\ndef wrong(): Int = choose(1, \"x\")", "arg type mismatch") && Check.check("""def choose[A](x: A, y: A): A = x def right(): Int = choose(1, 2)""") == "[]" && rejects("""def identity[A](x: A): A = x -def wrong(): String = identity(1)""", "does not match declared") && rejects("def id[A](x: A): Int = x", "does not match declared") && rejects("def id[A](x: A): A = 1", "does not match declared") && Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") == "[]" && Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") == "[]" && rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got") && rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch") && rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs") && rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare") && rejects("def wrong[A](x: A): Int = -x", "expected Int or Float") && Check.check("def right(): Int = (n => n + 1)(1)") == "[]" +def wrong(): String = identity(1)""", "does not match declared") && rejects("def id[A](x: A): Int = x", "does not match declared") && rejects("def id[A](x: A): A = 1", "does not match declared") && Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") == "[]" && Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") == "[]" && rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got") && rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch") && rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs") && rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare") && rejects("def wrong[A](x: A): Int = -x", "expected Int or Float") && Check.check("def right(): Int = (n => n + 1)(1)") == "[]" && ioPinTypes() + +def ioPinTypes(): Bool = + rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO") && rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO") && rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function") && rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO") && Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") == "[]" def listFilterTypes(): Bool = Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared") && rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare") && Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") == "[]" && rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared") && Check.check("def right(): List[String] = List.takeWhile([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && Check.check("def right(): List[String] = List.dropRight([\"a\", \"b\"], 1)") == "[]" def dumpUserTypeArgs(): String = - if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else "userTypeArgs-other" + if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else if (!rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO")) Str.concat("flatA ", Check.check("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO")) Str.concat("handleA ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)")) else if (!rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function")) Str.concat("applyA ", Check.check("def wrong[A](x: A): Int = x.apply(1)")) else if (!rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO")) Str.concat("resA ", Check.check("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") != "[]") Str.concat("flatOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))")) else "userTypeArgs-other" def dumpListFilter(): String = if (!rejects("def wrong(): List[Int] = [1, \"x\"]", "list element")) "listElem" else if (!rejects("def wrong(): List[Int] = List.concat([1], [\"x\"])", "arg type mismatch")) "listConcat" else if (!rejects("def wrong(): List[String] = List.map([1], x => x + 1)", "does not match declared")) "listMap" else if (Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") != "[]") Str.concat("filterOk ", Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)")) else if (!rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared")) Str.concat("filterMis ", Check.check("def wrong(): List[Int] = List.filter([\"a\"], x => true)")) else if (!rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare")) Str.concat("filterPred ", Check.check("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)")) else if (Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") != "[]") Str.concat("takeOk ", Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)")) else if (!rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared")) Str.concat("takeMis ", Check.check("def wrong(): List[Int] = List.take([\"a\"], 1)")) else "listFilter-other" From 9a9eeedf5eb02ea60218f5b3c36ad5a64cb42fd7 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 17 Sep 2026 03:34:22 -0400 Subject: [PATCH 17/23] Reject View.each, require, and Property.check on a generic parameter. Pinned A still unified with View and String through Type.eq, so a View.each body of type A typechecked. --- examples/compiler/src/Check.scuzz | 38 +++++++++++++++---------------- examples/tyck/src/Main.scuzz | 7 ++++-- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index 758af40c..de856423 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -567,10 +567,10 @@ def resolveNetLam(f: String, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens } def resolveNetAnn(a: Out, f: String, p: String, body: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = - if (hasErr(a)) a else resolveNetBody(f, infer(body, bindRes(p, bindOfAnn(a), env), funs, ens), span) + if (hasErr(a)) a else resolveNetBody(f, infer(body, bindRes(p, bindOfAnn(a), env), funs, ens), span, ens) -def resolveNetBody(f: String, body: Out, span: (String, Int)): Out = - if (hasErr(body)) body else if (isIoHttpResp(tyStr(body)) || isLoose(tyStr(body))) ok("IO[Unit]") else bad(Str.concat(f, Str.concat(" needs IO[(Int, Map[String, String], String)], got ", tyStr(body))), span) +def resolveNetBody(f: String, body: Out, span: (String, Int), ens: List[En]): Out = + if (hasErr(body)) body else if (isIoHttpResp(tyStr(body)) || isLooseEn(tyStr(body), ens)) ok("IO[Unit]") else bad(Str.concat(f, Str.concat(" needs IO[(Int, Map[String, String], String)], got ", tyStr(body))), span) def resolveNetFun(f: String, o: Out, span: (String, Int)): Out = if (hasErr(o)) o else if (netFunOk(tyStr(o))) ok("IO[Unit]") else bad(Str.concat(f, " needs a lambda"), span) @@ -595,26 +595,26 @@ def resolveViewEachLam(sigTy: Ty, lam: Expr, env: List[(String, Ty)], funs: Ftab case Expr.ELam(p, ty, body) => resolveViewEachAnn(annOk(ty, eachElemOf(sigTy), span), p, body, env, funs, ens, span) case Expr.ENamed(_, inner) => resolveViewEachLam(sigTy, inner, env, funs, ens, span) case Expr.EAscribe(inner, _, _) => resolveViewEachLam(sigTy, inner, env, funs, ens, span) - case _ => resolveViewEachFun(infer(stripNamed(lam), env, funs, ens), eachElemOf(sigTy), span) + case _ => resolveViewEachFun(infer(stripNamed(lam), env, funs, ens), eachElemOf(sigTy), span, ens) } def resolveViewEachAnn(a: Out, p: String, body: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = - if (hasErr(a)) a else resolveViewEachBody(infer(body, bindRes(p, bindOfAnn(a), env), funs, ens), span) + if (hasErr(a)) a else resolveViewEachBody(infer(body, bindRes(p, bindOfAnn(a), env), funs, ens), span, ens) -def resolveViewEachFun(o: Out, elem: String, span: (String, Int)): Out = - if (hasErr(o)) o else if (Type.isFun(o.ty) && tyEq(Type.show(Type.funArg(o.ty)), elem) && (tyEq(Type.show(Type.funRet(o.ty)), "View") || isLoose(Type.show(Type.funRet(o.ty))))) ok("View") else bad("View.each needs A => View", span) +def resolveViewEachFun(o: Out, elem: String, span: (String, Int), ens: List[En]): Out = + if (hasErr(o)) o else if (Type.isFun(o.ty) && tyEq(Type.show(Type.funArg(o.ty)), elem) && (tyEqEn(Type.show(Type.funRet(o.ty)), "View", ens) || isLooseEn(Type.show(Type.funRet(o.ty)), ens))) ok("View") else bad("View.each needs A => View", span) -def resolveViewEachBody(body: Out, span: (String, Int)): Out = - if (hasErr(body)) body else if (tyEq(tyStr(body), "View") || isLoose(tyStr(body))) ok("View") else bad(Str.concat("View.each needs View, got ", tyStr(body)), span) +def resolveViewEachBody(body: Out, span: (String, Int), ens: List[En]): Out = + if (hasErr(body)) body else if (tyEqEn(tyStr(body), "View", ens) || isLooseEn(tyStr(body), ens)) ok("View") else bad(Str.concat("View.each needs View, got ", tyStr(body)), span) def resolvePropCheck(args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = if (List.len(args) != 3) bad(Str.concat("Property.check expects 3 args, got ", Str.fromInt(List.len(args))), span) else resolvePropCheckName(infer(stripNamed(List.at(args, 0)), env, funs, ens), List.tail(args), env, funs, ens, span) def resolvePropCheckName(name: Out, rest: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = - if (hasErr(name)) name else if (tyEq(tyStr(name), "String") || isLoose(tyStr(name))) resolvePropCheckPred(infer(stripNamed(List.at(rest, 0)), env, funs, ens), List.tail(rest), env, funs, ens, span) else argMismatch("Property.check", "String", tyStr(name), span) + if (hasErr(name)) name else if (tyEqEn(tyStr(name), "String", ens) || isLooseEn(tyStr(name), ens)) resolvePropCheckPred(infer(stripNamed(List.at(rest, 0)), env, funs, ens), List.tail(rest), env, funs, ens, span) else argMismatch("Property.check", "String", tyStr(name), span) def resolvePropCheckPred(pred: Out, rest: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = - if (hasErr(pred)) pred else if (requirePredOk(tyStr(pred))) resolvePropCheckVal(infer(stripNamed(List.at(rest, 0)), env, funs, ens), span) else argMismatch("Property.check", "Bool", tyStr(pred), span) + if (hasErr(pred)) pred else if (requirePredOk(tyStr(pred), ens)) resolvePropCheckVal(infer(stripNamed(List.at(rest, 0)), env, funs, ens), span) else argMismatch("Property.check", "Bool", tyStr(pred), span) def resolvePropCheckVal(val: Out, span: (String, Int)): Out = if (hasErr(val)) val else okTy(val.ty) @@ -1472,22 +1472,22 @@ def requirePredExpr(args: List[Expr]): Expr = def inferRequirePred(recv: Out, pred: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = pred match { - case Expr.ELam(p, ty, body) => inferRequireLam(recv, infer(body, bindLam(p, requireBindTy(ty, tyStr(recv)), env), funs, ens), env, e) + case Expr.ELam(p, ty, body) => inferRequireLam(recv, infer(body, bindLam(p, requireBindTy(ty, tyStr(recv)), env), funs, ens), env, ens, e) case Expr.ENamed(_, inner) => inferRequirePred(recv, inner, env, funs, ens, e) - case _ => inferRequirePredTy(recv, infer(pred, env, funs, ens), env, e) + case _ => inferRequirePredTy(recv, infer(pred, env, funs, ens), env, ens, e) } def requireBindTy(ann: String, recvTy: String): String = if (ann != "") ann else if (isIo(recvTy)) ioOk(recvTy) else recvTy -def inferRequireLam(recv: Out, body: Out, env: List[(String, Ty)], e: Expr): Out = - if (hasErr(body)) body else if (tyStr(body) == "Bool" || isLoose(tyStr(body))) okTy(recv.ty) else bad(Str.concat("require pred must be Bool, got ", tyStr(body)), exprSpan(env, e)) +def inferRequireLam(recv: Out, body: Out, env: List[(String, Ty)], ens: List[En], e: Expr): Out = + if (hasErr(body)) body else if (tyStr(body) == "Bool" || isLooseEn(tyStr(body), ens)) okTy(recv.ty) else bad(Str.concat("require pred must be Bool, got ", tyStr(body)), exprSpan(env, e)) -def inferRequirePredTy(recv: Out, po: Out, env: List[(String, Ty)], e: Expr): Out = - if (hasErr(po)) po else if (requirePredOk(tyStr(po))) okTy(recv.ty) else bad(Str.concat("require pred must be Bool, IO[Bool], or A => Bool, got ", tyStr(po)), exprSpan(env, e)) +def inferRequirePredTy(recv: Out, po: Out, env: List[(String, Ty)], ens: List[En], e: Expr): Out = + if (hasErr(po)) po else if (requirePredOk(tyStr(po), ens)) okTy(recv.ty) else bad(Str.concat("require pred must be Bool, IO[Bool], or A => Bool, got ", tyStr(po)), exprSpan(env, e)) -def requirePredOk(ty: String): Bool = - ty == "Bool" || isLoose(ty) || isIo(ty) && (ioOk(ty) == "Bool" || isLoose(ioOk(ty))) || isFunTy(ty) && (funRetOf(ty) == "Bool" || isLoose(funRetOf(ty))) +def requirePredOk(ty: String, ens: List[En]): Bool = + ty == "Bool" || isLooseEn(ty, ens) || isIo(ty) && (ioOk(ty) == "Bool" || isLooseEn(ioOk(ty), ens)) || isFunTy(ty) && (funRetOf(ty) == "Bool" || isLooseEn(funRetOf(ty), ens)) def inferApply(recv: Expr, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = recv match { diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index 912be72a..fadb46a3 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -364,13 +364,16 @@ def right(): Int = choose(1, 2)""") == "[]" && rejects("""def identity[A](x: A): def wrong(): String = identity(1)""", "does not match declared") && rejects("def id[A](x: A): Int = x", "does not match declared") && rejects("def id[A](x: A): A = 1", "does not match declared") && Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") == "[]" && Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") == "[]" && rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got") && rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch") && rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs") && rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare") && rejects("def wrong[A](x: A): Int = -x", "expected Int or Float") && Check.check("def right(): Int = (n => n + 1)(1)") == "[]" && ioPinTypes() def ioPinTypes(): Bool = - rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO") && rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO") && rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function") && rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO") && Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") == "[]" + rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO") && rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO") && rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function") && rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO") && Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") == "[]" && viewPinTypes() + +def viewPinTypes(): Bool = + rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View") && rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool") && rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch") && Check.check("def right(xs: Signal[List[Int]]): View = View.each(xs, n => View.text(Str.fromInt(n)))") == "[]" && Check.check("def right(n: Int): Int = n.require(true)") == "[]" def listFilterTypes(): Bool = Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared") && rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare") && Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") == "[]" && rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared") && Check.check("def right(): List[String] = List.takeWhile([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && Check.check("def right(): List[String] = List.dropRight([\"a\", \"b\"], 1)") == "[]" def dumpUserTypeArgs(): String = - if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else if (!rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO")) Str.concat("flatA ", Check.check("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO")) Str.concat("handleA ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)")) else if (!rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function")) Str.concat("applyA ", Check.check("def wrong[A](x: A): Int = x.apply(1)")) else if (!rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO")) Str.concat("resA ", Check.check("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") != "[]") Str.concat("flatOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))")) else "userTypeArgs-other" + if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else if (!rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO")) Str.concat("flatA ", Check.check("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO")) Str.concat("handleA ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)")) else if (!rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function")) Str.concat("applyA ", Check.check("def wrong[A](x: A): Int = x.apply(1)")) else if (!rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO")) Str.concat("resA ", Check.check("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") != "[]") Str.concat("flatOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View")) Str.concat("eachA ", Check.check("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)")) else if (!rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool")) Str.concat("reqA ", Check.check("def wrong[A](n: Int, p: A): Int = n.require(p)")) else if (!rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch")) Str.concat("propA ", Check.check("def wrong[A](x: A): Int = Property.check(x, true, 1)")) else "userTypeArgs-other" def dumpListFilter(): String = if (!rejects("def wrong(): List[Int] = [1, \"x\"]", "list element")) "listElem" else if (!rejects("def wrong(): List[Int] = List.concat([1], [\"x\"])", "arg type mismatch")) "listConcat" else if (!rejects("def wrong(): List[String] = List.map([1], x => x + 1)", "does not match declared")) "listMap" else if (Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") != "[]") Str.concat("filterOk ", Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)")) else if (!rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared")) Str.concat("filterMis ", Check.check("def wrong(): List[Int] = List.filter([\"a\"], x => true)")) else if (!rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare")) Str.concat("filterPred ", Check.check("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)")) else if (Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") != "[]") Str.concat("takeOk ", Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)")) else if (!rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared")) Str.concat("takeMis ", Check.check("def wrong(): List[Int] = List.take([\"a\"], 1)")) else "listFilter-other" From d193e1efba72b497971c2db7f697b20815594c03 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 17 Sep 2026 04:35:56 -0400 Subject: [PATCH 18/23] Reject IO payload unification of a generic parameter with a concrete type. handleErrorWith, fail-channel unify, and lambda annotations now pin against the enclosing def so A does not match Int. --- examples/compiler/src/Check.scuzz | 34 +++++++++++++++---------------- examples/tyck/src/Main.scuzz | 7 +++++-- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index de856423..be7c9550 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -79,8 +79,8 @@ def bindParamsHd(p: Param, rest: List[Param], acc: List[(String, Ty)]): List[(St def bindLam(p: String, ty: String, env: List[(String, Ty)]): List[(String, Ty)] = envBind(p, ty, env) -def annOk(ann: String, expect: String, span: (String, Int)): Out = - if (ann == "" || tyEq(ann, expect)) ok(expect) else bad(Str.concat("type mismatch: expected ", Str.concat(ann, Str.concat(", got ", expect))), span) +def annOk(ann: String, expect: String, span: (String, Int), ens: List[En]): Out = + if (ann == "" || tyEqEn(ann, expect, ens)) ok(expect) else bad(Str.concat("type mismatch: expected ", Str.concat(ann, Str.concat(", got ", expect))), span) def lookupEnv(env: List[(String, Ty)], n: String): Ty = if (List.isEmpty(env)) Type.tNamed("") else lookupEnv1(List.at(env, 0), List.tail(env), n) @@ -526,7 +526,7 @@ def resolveResUse(ao: Out, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: def resolveResLam(recv: String, bind: String, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int), makeRet: String): Out = lam match { - case Expr.ELam(p, ty, body) => resolveResAnn(annOk(ty, bind, span), recv, p, body, env, funs, ens, span, makeRet) + case Expr.ELam(p, ty, body) => resolveResAnn(annOk(ty, bind, span, ens), recv, p, body, env, funs, ens, span, makeRet) case Expr.ENamed(_, inner) => resolveResLam(recv, bind, inner, env, funs, ens, span, makeRet) case _ => bad(Str.concat(if (makeRet == "") "Resource.use" else "Resource.make", " needs a lambda"), span) } @@ -560,7 +560,7 @@ def resolveNetServe2(f: String, po: Out, lam: Expr, env: List[(String, Ty)], fun def resolveNetLam(f: String, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = lam match { - case Expr.ELam(p, ty, body) => resolveNetAnn(annOk(ty, netReqTy(), span), f, p, body, env, funs, ens, span) + case Expr.ELam(p, ty, body) => resolveNetAnn(annOk(ty, netReqTy(), span, ens), f, p, body, env, funs, ens, span) case Expr.ENamed(_, inner) => resolveNetLam(f, inner, env, funs, ens, span) case Expr.EAscribe(inner, _, _) => resolveNetLam(f, inner, env, funs, ens, span) case _ => resolveNetFun(f, infer(stripNamed(lam), env, funs, ens), span) @@ -592,7 +592,7 @@ def resolveViewEach2(src: Out, rest: List[Expr], env: List[(String, Ty)], funs: def resolveViewEachLam(sigTy: Ty, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = lam match { - case Expr.ELam(p, ty, body) => resolveViewEachAnn(annOk(ty, eachElemOf(sigTy), span), p, body, env, funs, ens, span) + case Expr.ELam(p, ty, body) => resolveViewEachAnn(annOk(ty, eachElemOf(sigTy), span, ens), p, body, env, funs, ens, span) case Expr.ENamed(_, inner) => resolveViewEachLam(sigTy, inner, env, funs, ens, span) case Expr.EAscribe(inner, _, _) => resolveViewEachLam(sigTy, inner, env, funs, ens, span) case _ => resolveViewEachFun(infer(stripNamed(lam), env, funs, ens), eachElemOf(sigTy), span, ens) @@ -892,7 +892,7 @@ def inferForBind2(d: Bool, name: String, vo: Out, rest: List[Bind], body: Expr, if (hasErr(vo)) vo else if (d) inferForDraw(name, tyStr(vo), rest, body, env, funs, ens, errTy, span) else inferForGo(rest, body, bindFor(false, name, tyStr(vo), env, ens), funs, ens, errTy) def inferForDraw(name: String, ty: String, rest: List[Bind], body: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], errTy: String, span: (String, Int)): Out = - if (Str.startsWith(ty, "Stream") || Str.startsWith(ty, "Resource")) bad(Str.concat("<- needs IO, got ", ty), span) else inferForDraw2(name, ty, rest, body, env, funs, ens, unifyErr(errTy, ioErr(ty), span)) + if (Str.startsWith(ty, "Stream") || Str.startsWith(ty, "Resource")) bad(Str.concat("<- needs IO, got ", ty), span) else inferForDraw2(name, ty, rest, body, env, funs, ens, unifyErr(errTy, ioErr(ty), span, ens)) def inferForDraw2(name: String, ty: String, rest: List[Bind], body: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], u: Out): Out = if (hasErr(u)) u else inferForGo(rest, body, bindFor(true, name, ty, env, ens), funs, ens, tyStr(u)) @@ -1566,8 +1566,8 @@ def ioOk(t: String): String = def ioTy(e: String, a: String): String = if (e == "" || e == "String") Str.concat("IO[", Str.concat(a, "]")) else Str.concat("IO[", Str.concat(e, Str.concat(", ", Str.concat(a, "]")))) -def unifyErr(a: String, b: String, span: (String, Int)): Out = - if (a == "") ok(if (b == "") "String" else b) else if (b == "") ok(a) else if (tyEq(a, b)) ok(prefer(a, b)) else bad(Str.concat("fail type mismatch: expected ", Str.concat(a, Str.concat(", got ", b))), span) +def unifyErr(a: String, b: String, span: (String, Int), ens: List[En]): Out = + if (a == "") ok(if (b == "") "String" else b) else if (b == "") ok(a) else if (tyEqEn(a, b, ens)) ok(preferEn(a, b, ens)) else bad(Str.concat("fail type mismatch: expected ", Str.concat(a, Str.concat(", got ", b))), span) def inferFlatMap(recv: Expr, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = inferFlatMap2(infer(recv, env, funs, ens), args, env, funs, ens, e) @@ -1580,7 +1580,7 @@ def inferIoMap2(o: Out, recv: Expr, args: List[Expr], env: List[(String, Ty)], f def inferIoMapLam(recv: Out, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = lam match { - case Expr.ELam(p, ty, body) => inferIoMapAnn(annOk(ty, ioPayload(tyStr(recv)), exprSpan(env, e)), recv, p, body, env, funs, ens, e) + case Expr.ELam(p, ty, body) => inferIoMapAnn(annOk(ty, ioPayload(tyStr(recv)), exprSpan(env, e), ens), recv, p, body, env, funs, ens, e) case Expr.EMatch(_, _, _) => inferIoMap3(recv, infer(lam, env, funs, ens), env, e) case _ => bad("map needs a lambda", exprSpan(env, e)) } @@ -1596,7 +1596,7 @@ def inferFlatMap2(o: Out, args: List[Expr], env: List[(String, Ty)], funs: Ftab, def inferFlatMapLam(recv: Out, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = lam match { - case Expr.ELam(p, ty, body) => inferFlatMapAnn(annOk(ty, ioPayload(tyStr(recv)), exprSpan(env, e)), recv, p, body, env, funs, ens, e) + case Expr.ELam(p, ty, body) => inferFlatMapAnn(annOk(ty, ioPayload(tyStr(recv)), exprSpan(env, e), ens), recv, p, body, env, funs, ens, e) case _ => inferFlatMap3(recv, infer(lam, env, funs, ens), env, ens, e) } @@ -1613,10 +1613,10 @@ def inferFlatMap3(recv: Out, body: Out, env: List[(String, Ty)], ens: List[En], if (hasErr(body)) body else inferFlatMap4(recv, body, env, ens, e) def inferFlatMap4(recv: Out, body: Out, env: List[(String, Ty)], ens: List[En], e: Expr): Out = - if (isIo(tyStr(recv)) && isIo(tyStr(body))) inferFlatMapErr(recv, body, env, e) else if (isIo(tyStr(recv)) && isLooseEn(tyStr(body), ens)) okTy(recv.ty) else if (isLooseEn(tyStr(recv), ens)) ok(if (isIo(tyStr(body))) tyStr(body) else "IO[A]") else bad(Str.concat("flatMap needs IO, got ", Str.concat(tyStr(recv), Str.concat(" and ", tyStr(body)))), exprSpan(env, e)) + if (isIo(tyStr(recv)) && isIo(tyStr(body))) inferFlatMapErr(recv, body, env, ens, e) else if (isIo(tyStr(recv)) && isLooseEn(tyStr(body), ens)) okTy(recv.ty) else if (isLooseEn(tyStr(recv), ens)) ok(if (isIo(tyStr(body))) tyStr(body) else "IO[A]") else bad(Str.concat("flatMap needs IO, got ", Str.concat(tyStr(recv), Str.concat(" and ", tyStr(body)))), exprSpan(env, e)) -def inferFlatMapErr(recv: Out, body: Out, env: List[(String, Ty)], e: Expr): Out = - inferFlatMapErr2(unifyErr(ioErr(tyStr(recv)), ioErr(tyStr(body)), exprSpan(env, e)), recv, body) +def inferFlatMapErr(recv: Out, body: Out, env: List[(String, Ty)], ens: List[En], e: Expr): Out = + inferFlatMapErr2(unifyErr(ioErr(tyStr(recv)), ioErr(tyStr(body)), exprSpan(env, e), ens), recv, body) def inferFlatMapErr2(u: Out, recv: Out, body: Out): Out = if (hasErr(u)) u else ok(ioTy(tyStr(u), ioOk(tyStr(body)))) @@ -1629,7 +1629,7 @@ def inferHandle2(o: Out, args: List[Expr], env: List[(String, Ty)], funs: Ftab, def inferHandleLam(recv: Out, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], e: Expr): Out = lam match { - case Expr.ELam(p, ty, body) => inferHandleAnn(annOk(ty, ioErr(tyStr(recv)), exprSpan(env, e)), recv, p, body, env, funs, ens, e) + case Expr.ELam(p, ty, body) => inferHandleAnn(annOk(ty, ioErr(tyStr(recv)), exprSpan(env, e), ens), recv, p, body, env, funs, ens, e) case _ => inferHandle3(recv, infer(lam, env, funs, ens), env, ens, e) } @@ -1640,10 +1640,10 @@ def bindHandle(p: String, ioTy: String, env: List[(String, Ty)]): List[(String, envBind(p, ioErr(ioTy), env) def inferHandle3(recv: Out, body: Out, env: List[(String, Ty)], ens: List[En], e: Expr): Out = - if (hasErr(body)) body else if (isIo(tyStr(recv)) && isIo(tyStr(body))) inferHandleOk(recv, body, env, e) else if (isIo(tyStr(recv)) && isLooseEn(tyStr(body), ens)) okTy(recv.ty) else bad(Str.concat("handleErrorWith needs IO, got ", Str.concat(tyStr(recv), Str.concat(" and ", tyStr(body)))), exprSpan(env, e)) + if (hasErr(body)) body else if (isIo(tyStr(recv)) && isIo(tyStr(body))) inferHandleOk(recv, body, env, ens, e) else if (isIo(tyStr(recv)) && isLooseEn(tyStr(body), ens)) okTy(recv.ty) else bad(Str.concat("handleErrorWith needs IO, got ", Str.concat(tyStr(recv), Str.concat(" and ", tyStr(body)))), exprSpan(env, e)) -def inferHandleOk(recv: Out, body: Out, env: List[(String, Ty)], e: Expr): Out = - if (tyEq(ioOk(tyStr(recv)), ioOk(tyStr(body)))) ok(ioTy(ioErr(tyStr(body)), prefer(ioOk(tyStr(recv)), ioOk(tyStr(body))))) else bad(Str.concat("handleErrorWith type mismatch: expected ", Str.concat(ioOk(tyStr(recv)), Str.concat(", got ", ioOk(tyStr(body))))), exprSpan(env, e)) +def inferHandleOk(recv: Out, body: Out, env: List[(String, Ty)], ens: List[En], e: Expr): Out = + if (tyEqEn(ioOk(tyStr(recv)), ioOk(tyStr(body)), ens)) ok(ioTy(ioErr(tyStr(body)), preferEn(ioOk(tyStr(recv)), ioOk(tyStr(body)), ens))) else bad(Str.concat("handleErrorWith type mismatch: expected ", Str.concat(ioOk(tyStr(recv)), Str.concat(", got ", ioOk(tyStr(body))))), exprSpan(env, e)) def inferVar(s: String, env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = inferVar2(s, lookupEnv(env, s), env, funs, ens, span) diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index fadb46a3..04c8ed4c 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -367,13 +367,16 @@ def ioPinTypes(): Bool = rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO") && rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO") && rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function") && rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO") && Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") == "[]" && viewPinTypes() def viewPinTypes(): Bool = - rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View") && rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool") && rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch") && Check.check("def right(xs: Signal[List[Int]]): View = View.each(xs, n => View.text(Str.fromInt(n)))") == "[]" && Check.check("def right(n: Int): Int = n.require(true)") == "[]" + rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View") && rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool") && rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch") && Check.check("def right(xs: Signal[List[Int]]): View = View.each(xs, n => View.text(Str.fromInt(n)))") == "[]" && Check.check("def right(n: Int): Int = n.require(true)") == "[]" && payloadPinTypes() + +def payloadPinTypes(): Bool = + rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))", "handleErrorWith type mismatch") && rejects("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)", "handleErrorWith type mismatch") && rejects("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))", "type mismatch: expected") && Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)") == "[]" && Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") == "[]" def listFilterTypes(): Bool = Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared") && rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare") && Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") == "[]" && rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared") && Check.check("def right(): List[String] = List.takeWhile([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && Check.check("def right(): List[String] = List.dropRight([\"a\", \"b\"], 1)") == "[]" def dumpUserTypeArgs(): String = - if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else if (!rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO")) Str.concat("flatA ", Check.check("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO")) Str.concat("handleA ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)")) else if (!rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function")) Str.concat("applyA ", Check.check("def wrong[A](x: A): Int = x.apply(1)")) else if (!rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO")) Str.concat("resA ", Check.check("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") != "[]") Str.concat("flatOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View")) Str.concat("eachA ", Check.check("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)")) else if (!rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool")) Str.concat("reqA ", Check.check("def wrong[A](n: Int, p: A): Int = n.require(p)")) else if (!rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch")) Str.concat("propA ", Check.check("def wrong[A](x: A): Int = Property.check(x, true, 1)")) else "userTypeArgs-other" + if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else if (!rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO")) Str.concat("flatA ", Check.check("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO")) Str.concat("handleA ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)")) else if (!rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function")) Str.concat("applyA ", Check.check("def wrong[A](x: A): Int = x.apply(1)")) else if (!rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO")) Str.concat("resA ", Check.check("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") != "[]") Str.concat("flatOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View")) Str.concat("eachA ", Check.check("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)")) else if (!rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool")) Str.concat("reqA ", Check.check("def wrong[A](n: Int, p: A): Int = n.require(p)")) else if (!rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch")) Str.concat("propA ", Check.check("def wrong[A](x: A): Int = Property.check(x, true, 1)")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))", "handleErrorWith type mismatch")) Str.concat("handlePure ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))")) else if (!rejects("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)", "handleErrorWith type mismatch")) Str.concat("handleLit ", Check.check("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)")) else if (!rejects("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))", "type mismatch: expected")) Str.concat("flatAnn ", Check.check("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)") != "[]") Str.concat("handleOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)")) else "userTypeArgs-other" def dumpListFilter(): String = if (!rejects("def wrong(): List[Int] = [1, \"x\"]", "list element")) "listElem" else if (!rejects("def wrong(): List[Int] = List.concat([1], [\"x\"])", "arg type mismatch")) "listConcat" else if (!rejects("def wrong(): List[String] = List.map([1], x => x + 1)", "does not match declared")) "listMap" else if (Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") != "[]") Str.concat("filterOk ", Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)")) else if (!rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared")) Str.concat("filterMis ", Check.check("def wrong(): List[Int] = List.filter([\"a\"], x => true)")) else if (!rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare")) Str.concat("filterPred ", Check.check("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)")) else if (Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") != "[]") Str.concat("takeOk ", Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)")) else if (!rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared")) Str.concat("takeMis ", Check.check("def wrong(): List[Int] = List.take([\"a\"], 1)")) else "listFilter-other" From 734852521a9d9aba73148f47c93efa8858617e82 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 17 Sep 2026 05:33:02 -0400 Subject: [PATCH 19/23] Reject Net.serve and View.each when a generic parameter stands in for Int or the element type. Port, HTTP response, handler, and View.each function-argument checks now pin against the enclosing def. --- examples/compiler/src/Check.scuzz | 20 ++++++++++---------- examples/tyck/src/Main.scuzz | 7 +++++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index be7c9550..7b4a19b7 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -546,34 +546,34 @@ def netReqTy(): String = def netRespTy(): String = "(Int, Map[String, String], String)" -def isIoHttpResp(t: String): Bool = - isIo(t) && tyEq(ioOk(t), netRespTy()) +def isIoHttpResp(t: String, ens: List[En]): Bool = + isIo(t) && tyEqEn(ioOk(t), netRespTy(), ens) -def netFunOk(ty: String): Bool = - isFunTy(ty) && tyEq(funArgOf(ty), netReqTy()) && isIoHttpResp(funRetOf(ty)) +def netFunOk(ty: String, ens: List[En]): Bool = + isFunTy(ty) && tyEqEn(funArgOf(ty), netReqTy(), ens) && isIoHttpResp(funRetOf(ty), ens) def resolveNetServe(f: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = if (List.isEmpty(args)) ok(funTyFrom(kitParams(f), kitRet(f))) else if (List.len(args) != 2) bad(Str.concat(f, Str.concat(" expects 2 args, got ", Str.fromInt(List.len(args)))), span) else resolveNetServe2(f, infer(stripNamed(List.at(args, 0)), env, funs, ens), List.at(args, 1), env, funs, ens, span) def resolveNetServe2(f: String, po: Out, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = - if (hasErr(po)) po else if (tyEq(tyStr(po), "Int")) resolveNetLam(f, lam, env, funs, ens, span) else argMismatch(f, "Int", tyStr(po), span) + if (hasErr(po)) po else if (tyEqEn(tyStr(po), "Int", ens)) resolveNetLam(f, lam, env, funs, ens, span) else argMismatch(f, "Int", tyStr(po), span) def resolveNetLam(f: String, lam: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = lam match { case Expr.ELam(p, ty, body) => resolveNetAnn(annOk(ty, netReqTy(), span, ens), f, p, body, env, funs, ens, span) case Expr.ENamed(_, inner) => resolveNetLam(f, inner, env, funs, ens, span) case Expr.EAscribe(inner, _, _) => resolveNetLam(f, inner, env, funs, ens, span) - case _ => resolveNetFun(f, infer(stripNamed(lam), env, funs, ens), span) + case _ => resolveNetFun(f, infer(stripNamed(lam), env, funs, ens), span, ens) } def resolveNetAnn(a: Out, f: String, p: String, body: Expr, env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int)): Out = if (hasErr(a)) a else resolveNetBody(f, infer(body, bindRes(p, bindOfAnn(a), env), funs, ens), span, ens) def resolveNetBody(f: String, body: Out, span: (String, Int), ens: List[En]): Out = - if (hasErr(body)) body else if (isIoHttpResp(tyStr(body)) || isLooseEn(tyStr(body), ens)) ok("IO[Unit]") else bad(Str.concat(f, Str.concat(" needs IO[(Int, Map[String, String], String)], got ", tyStr(body))), span) + if (hasErr(body)) body else if (isIoHttpResp(tyStr(body), ens) || isLooseEn(tyStr(body), ens)) ok("IO[Unit]") else bad(Str.concat(f, Str.concat(" needs IO[(Int, Map[String, String], String)], got ", tyStr(body))), span) -def resolveNetFun(f: String, o: Out, span: (String, Int)): Out = - if (hasErr(o)) o else if (netFunOk(tyStr(o))) ok("IO[Unit]") else bad(Str.concat(f, " needs a lambda"), span) +def resolveNetFun(f: String, o: Out, span: (String, Int), ens: List[En]): Out = + if (hasErr(o)) o else if (netFunOk(tyStr(o), ens)) ok("IO[Unit]") else bad(Str.concat(f, " needs a lambda"), span) def isListHead(t: Ty): Bool = Type.isNamed(t, "List") || Type.isAppNamed(t, "List") @@ -602,7 +602,7 @@ def resolveViewEachAnn(a: Out, p: String, body: Expr, env: List[(String, Ty)], f if (hasErr(a)) a else resolveViewEachBody(infer(body, bindRes(p, bindOfAnn(a), env), funs, ens), span, ens) def resolveViewEachFun(o: Out, elem: String, span: (String, Int), ens: List[En]): Out = - if (hasErr(o)) o else if (Type.isFun(o.ty) && tyEq(Type.show(Type.funArg(o.ty)), elem) && (tyEqEn(Type.show(Type.funRet(o.ty)), "View", ens) || isLooseEn(Type.show(Type.funRet(o.ty)), ens))) ok("View") else bad("View.each needs A => View", span) + if (hasErr(o)) o else if (Type.isFun(o.ty) && tyEqEn(Type.show(Type.funArg(o.ty)), elem, ens) && (tyEqEn(Type.show(Type.funRet(o.ty)), "View", ens) || isLooseEn(Type.show(Type.funRet(o.ty)), ens))) ok("View") else bad("View.each needs A => View", span) def resolveViewEachBody(body: Out, span: (String, Int), ens: List[En]): Out = if (hasErr(body)) body else if (tyEqEn(tyStr(body), "View", ens) || isLooseEn(tyStr(body), ens)) ok("View") else bad(Str.concat("View.each needs View, got ", tyStr(body)), span) diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index 04c8ed4c..45e41a1d 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -370,13 +370,16 @@ def viewPinTypes(): Bool = rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View") && rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool") && rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch") && Check.check("def right(xs: Signal[List[Int]]): View = View.each(xs, n => View.text(Str.fromInt(n)))") == "[]" && Check.check("def right(n: Int): Int = n.require(true)") == "[]" && payloadPinTypes() def payloadPinTypes(): Bool = - rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))", "handleErrorWith type mismatch") && rejects("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)", "handleErrorWith type mismatch") && rejects("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))", "type mismatch: expected") && Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)") == "[]" && Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") == "[]" + rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))", "handleErrorWith type mismatch") && rejects("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)", "handleErrorWith type mismatch") && rejects("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))", "type mismatch: expected") && Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)") == "[]" && Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") == "[]" && netPinTypes() + +def netPinTypes(): Bool = + rejects("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))", "arg type mismatch") && rejects("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)", "needs IO[(Int, Map[String, String], String)]") && rejects("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)", "View.each needs A => View") && Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))") == "[]" def listFilterTypes(): Bool = Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared") && rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare") && Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") == "[]" && rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared") && Check.check("def right(): List[String] = List.takeWhile([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && Check.check("def right(): List[String] = List.dropRight([\"a\", \"b\"], 1)") == "[]" def dumpUserTypeArgs(): String = - if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else if (!rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO")) Str.concat("flatA ", Check.check("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO")) Str.concat("handleA ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)")) else if (!rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function")) Str.concat("applyA ", Check.check("def wrong[A](x: A): Int = x.apply(1)")) else if (!rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO")) Str.concat("resA ", Check.check("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") != "[]") Str.concat("flatOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View")) Str.concat("eachA ", Check.check("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)")) else if (!rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool")) Str.concat("reqA ", Check.check("def wrong[A](n: Int, p: A): Int = n.require(p)")) else if (!rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch")) Str.concat("propA ", Check.check("def wrong[A](x: A): Int = Property.check(x, true, 1)")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))", "handleErrorWith type mismatch")) Str.concat("handlePure ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))")) else if (!rejects("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)", "handleErrorWith type mismatch")) Str.concat("handleLit ", Check.check("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)")) else if (!rejects("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))", "type mismatch: expected")) Str.concat("flatAnn ", Check.check("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)") != "[]") Str.concat("handleOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)")) else "userTypeArgs-other" + if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else if (!rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO")) Str.concat("flatA ", Check.check("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO")) Str.concat("handleA ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)")) else if (!rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function")) Str.concat("applyA ", Check.check("def wrong[A](x: A): Int = x.apply(1)")) else if (!rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO")) Str.concat("resA ", Check.check("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") != "[]") Str.concat("flatOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View")) Str.concat("eachA ", Check.check("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)")) else if (!rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool")) Str.concat("reqA ", Check.check("def wrong[A](n: Int, p: A): Int = n.require(p)")) else if (!rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch")) Str.concat("propA ", Check.check("def wrong[A](x: A): Int = Property.check(x, true, 1)")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))", "handleErrorWith type mismatch")) Str.concat("handlePure ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))")) else if (!rejects("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)", "handleErrorWith type mismatch")) Str.concat("handleLit ", Check.check("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)")) else if (!rejects("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))", "type mismatch: expected")) Str.concat("flatAnn ", Check.check("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)") != "[]") Str.concat("handleOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)")) else if (!rejects("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))", "arg type mismatch")) Str.concat("netPort ", Check.check("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))")) else if (!rejects("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)", "needs IO[(Int, Map[String, String], String)]")) Str.concat("netBody ", Check.check("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)", "View.each needs A => View")) Str.concat("eachFun ", Check.check("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)")) else if (Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))") != "[]") Str.concat("netOk ", Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))")) else "userTypeArgs-other" def dumpListFilter(): String = if (!rejects("def wrong(): List[Int] = [1, \"x\"]", "list element")) "listElem" else if (!rejects("def wrong(): List[Int] = List.concat([1], [\"x\"])", "arg type mismatch")) "listConcat" else if (!rejects("def wrong(): List[String] = List.map([1], x => x + 1)", "does not match declared")) "listMap" else if (Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") != "[]") Str.concat("filterOk ", Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)")) else if (!rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared")) Str.concat("filterMis ", Check.check("def wrong(): List[Int] = List.filter([\"a\"], x => true)")) else if (!rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare")) Str.concat("filterPred ", Check.check("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)")) else if (Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") != "[]") Str.concat("takeOk ", Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)")) else if (!rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared")) Str.concat("takeMis ", Check.check("def wrong(): List[Int] = List.take([\"a\"], 1)")) else "listFilter-other" From bae7984ffd1c42c0fad2eb114b6f34a690649b71 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 17 Sep 2026 06:33:16 -0400 Subject: [PATCH 20/23] Reject kit arguments that use a generic def parameter after substitution. Pin only the caller type so List.filter cannot take `_ => x` for `A`, while unbound kit parameters still instantiate. --- docs/gaps.md | 2 +- docs/philosophy.md | 2 +- examples/compiler/src/Check.scuzz | 10 +++++----- examples/compiler/src/Type.scuzz | 3 +++ examples/tyck/src/Main.scuzz | 7 +++++-- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/gaps.md b/docs/gaps.md index 79df5a6b..0f815f49 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -37,7 +37,7 @@ Do not add library publishing, git or registry deps, or `scuzz add`. Path deps s Resolve these gaps when they prevent ordinary language use. -1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions and payloads seen through a lambda parameter over a collection. Applying an env-bound lambda with an unresolved parameter letter to a concrete argument fails check. An unannotated lambda in argument or def-body position binds its parameter from the expected function type, so a loose lambda can no longer escape through an expected function type. A generic def pins its own type parameters for every check in its body. Param letters (`A`/`E`) still unify in `Type.eq` outside those pins. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Tuple components and constructor fields compare String, Int, and Bool literals. Constructor, tuple, cons, as, and `[]` patterns nest in those positions. +1. **Checker and emit residuals** — A Queue or Deferred payload pins at the first offer or complete in the same for-comprehension, including nested expressions and payloads seen through a lambda parameter over a collection. Applying an env-bound lambda with an unresolved parameter letter to a concrete argument fails check. An unannotated lambda in argument or def-body position binds its parameter from the expected function type, so a loose lambda can no longer escape through an expected function type. A generic def pins its own type parameters for every check in its body. Kit argument checks pin the caller type after substitution. Unbound kit parameters still match through `Type.eq`. Parse Param/Fun stay strings. A path-dep file over 40k keeps def heads with a stub body so Check can resolve a qualified call. Tuple components and constructor fields compare String, Int, and Bool literals. Constructor, tuple, cons, as, and `[]` patterns nest in those positions. 2. **Compile-time performance** — `scuzz check examples/compiler` is 17 s. A cold `scuzz build examples/tyck` is 34 s. Emitted string literals intern to pinned allocations. Remaining cost: RC retain/release churn and `sz_list_concat` in string building. Coverage still parses a compiled graph that differs from live. diff --git a/docs/philosophy.md b/docs/philosophy.md index 66600bae..33957ca6 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -124,7 +124,7 @@ Locks (not an API catalog — run `scuzz docs language` and `scuzz docs kits`): - Interpolated strings use the same escape rules as ordinary strings. Decode escapes in literal segments once. Parse expressions inside interpolation braces as source. Live code and verification use the same rules. - Optional `package`; top-level `def` / `private def` / `import`; `@main def …: IO[Unit]` - Payload enums + `record` sugar + thin traits/`impl` (static dispatch) + monomorphized generics -- A generic def pins its own type parameters for every check in its body. `A` does not match `Int` or `String` there. Call sites still instantiate parameters. +- A generic def pins its own type parameters for every check in its body. `A` does not match `Int` or `String` there. Call sites still instantiate parameters. Kit argument checks pin the caller type after substitution. They do not pin unbound kit parameters. - Record field lookup substitutes the receiver type arguments into the declared field type. The same rule applies inside callbacks. - Constructor patterns compare direct String, Int, and Bool literals before an arm runs. Named fields use their declared positions. A failed literal comparison tries the next arm. Constructor, tuple, cons, as, and `[]` patterns nest in constructor fields and tuple components. - Literal alternatives support chains of String, Int, or Bool values. Test each alternative before the arm guard. String contents can include the alternative separator. diff --git a/examples/compiler/src/Check.scuzz b/examples/compiler/src/Check.scuzz index 7b4a19b7..69e0b04b 100644 --- a/examples/compiler/src/Check.scuzz +++ b/examples/compiler/src/Check.scuzz @@ -14,9 +14,9 @@ import Parse.Alias import Parse.Imp import Parse.Im import Lexer.lex -import Type.eqStr import Type.preferStr import Type.eqPinned +import Type.eqPinGot import Type.preferPinned import Type.isTyParam import Type.Ty @@ -322,9 +322,6 @@ def argMismatchMsg(prefix: String, got: String): String = def isListTy(t: String): Bool = t == "List" || Str.startsWith(t, "List[") -def tyEq(a: String, b: String): Bool = - eqStr(a, b) - def enNames(ens: List[En]): List[String] = if (List.isEmpty(ens)) noStr() else enNamesHd(List.at(ens, 0), List.tail(ens)) @@ -336,6 +333,9 @@ def enNamesHd(h: En, rest: List[En]): List[String] = def tyEqEn(a: String, b: String, ens: List[En]): Bool = eqPinned(a, b, enNames(ens)) +def tyEqGot(want: String, got: String, ens: List[En]): Bool = + eqPinGot(want, got, enNames(ens)) + def pinEns(ens: List[En], ts: List[String]): List[En] = if (List.isEmpty(ts)) ens else En(false, "#pin", ts, noCase()) :: ens @@ -667,7 +667,7 @@ def genericArg(f: String, ts: List[String], want: List[String], ret: String, arg if (hasErr(got)) got else genericBound(f, ts, want, ret, args, env, funs, ens, span, tyStr(got), Type.bindings(Type.parse(List.at(want, 0)), got.ty, ts)) def genericBound(f: String, ts: List[String], want: List[String], ret: String, args: List[Expr], env: List[(String, Ty)], funs: Ftab, ens: List[En], span: (String, Int), got: String, bindings: List[(String, String)]): Out = - if (!tyEq(applyBindings(List.at(want, 0), bindings), got)) argMismatch(f, applyBindings(List.at(want, 0), bindings), got, span) else genericArgs(f, ts, bindWant(List.tail(want), bindings), applyBindings(ret, bindings), List.tail(args), env, funs, ens, span) + if (!tyEqGot(applyBindings(List.at(want, 0), bindings), got, ens)) argMismatch(f, applyBindings(List.at(want, 0), bindings), got, span) else genericArgs(f, ts, bindWant(List.tail(want), bindings), applyBindings(ret, bindings), List.tail(args), env, funs, ens, span) def applyBindings(ty: String, bindings: List[(String, String)]): String = if (List.isEmpty(bindings)) ty else applyBinding(ty, List.at(bindings, 0), List.tail(bindings)) diff --git a/examples/compiler/src/Type.scuzz b/examples/compiler/src/Type.scuzz index 77978cb6..846f6d2d 100644 --- a/examples/compiler/src/Type.scuzz +++ b/examples/compiler/src/Type.scuzz @@ -352,6 +352,9 @@ def substStr(target: String, param: String, arg: String): String = def eqPinned(a: String, b: String, names: List[String]): Bool = eq(pin(parse(a), names), pin(parse(b), names)) +def eqPinGot(want: String, got: String, names: List[String]): Bool = + eq(parse(want), pin(parse(got), names)) + def preferPinned(a: String, b: String, names: List[String]): String = show(prefer(pin(parse(a), names), pin(parse(b), names))) diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index 45e41a1d..7b14e9de 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -373,13 +373,16 @@ def payloadPinTypes(): Bool = rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))", "handleErrorWith type mismatch") && rejects("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)", "handleErrorWith type mismatch") && rejects("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))", "type mismatch: expected") && Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)") == "[]" && Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") == "[]" && netPinTypes() def netPinTypes(): Bool = - rejects("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))", "arg type mismatch") && rejects("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)", "needs IO[(Int, Map[String, String], String)]") && rejects("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)", "View.each needs A => View") && Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))") == "[]" + rejects("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))", "arg type mismatch") && rejects("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)", "needs IO[(Int, Map[String, String], String)]") && rejects("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)", "View.each needs A => View") && Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))") == "[]" && kitPinTypes() + +def kitPinTypes(): Bool = + rejects("def wrong[A](xs: List[Int], x: A): List[Int] = List.filter(xs, _ => x)", "arg type mismatch") && Check.check("def right[B](xs: List[Int]): List[String] = List.map(xs, n => \"x\")") == "[]" && Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") == "[]" def listFilterTypes(): Bool = Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared") && rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare") && Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") == "[]" && rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared") && Check.check("def right(): List[String] = List.takeWhile([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && Check.check("def right(): List[String] = List.dropRight([\"a\", \"b\"], 1)") == "[]" def dumpUserTypeArgs(): String = - if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else if (!rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO")) Str.concat("flatA ", Check.check("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO")) Str.concat("handleA ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)")) else if (!rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function")) Str.concat("applyA ", Check.check("def wrong[A](x: A): Int = x.apply(1)")) else if (!rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO")) Str.concat("resA ", Check.check("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") != "[]") Str.concat("flatOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View")) Str.concat("eachA ", Check.check("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)")) else if (!rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool")) Str.concat("reqA ", Check.check("def wrong[A](n: Int, p: A): Int = n.require(p)")) else if (!rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch")) Str.concat("propA ", Check.check("def wrong[A](x: A): Int = Property.check(x, true, 1)")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))", "handleErrorWith type mismatch")) Str.concat("handlePure ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))")) else if (!rejects("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)", "handleErrorWith type mismatch")) Str.concat("handleLit ", Check.check("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)")) else if (!rejects("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))", "type mismatch: expected")) Str.concat("flatAnn ", Check.check("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)") != "[]") Str.concat("handleOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)")) else if (!rejects("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))", "arg type mismatch")) Str.concat("netPort ", Check.check("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))")) else if (!rejects("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)", "needs IO[(Int, Map[String, String], String)]")) Str.concat("netBody ", Check.check("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)", "View.each needs A => View")) Str.concat("eachFun ", Check.check("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)")) else if (Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))") != "[]") Str.concat("netOk ", Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))")) else "userTypeArgs-other" + if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else if (!rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO")) Str.concat("flatA ", Check.check("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO")) Str.concat("handleA ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)")) else if (!rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function")) Str.concat("applyA ", Check.check("def wrong[A](x: A): Int = x.apply(1)")) else if (!rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO")) Str.concat("resA ", Check.check("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") != "[]") Str.concat("flatOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View")) Str.concat("eachA ", Check.check("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)")) else if (!rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool")) Str.concat("reqA ", Check.check("def wrong[A](n: Int, p: A): Int = n.require(p)")) else if (!rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch")) Str.concat("propA ", Check.check("def wrong[A](x: A): Int = Property.check(x, true, 1)")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))", "handleErrorWith type mismatch")) Str.concat("handlePure ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))")) else if (!rejects("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)", "handleErrorWith type mismatch")) Str.concat("handleLit ", Check.check("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)")) else if (!rejects("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))", "type mismatch: expected")) Str.concat("flatAnn ", Check.check("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)") != "[]") Str.concat("handleOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)")) else if (!rejects("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))", "arg type mismatch")) Str.concat("netPort ", Check.check("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))")) else if (!rejects("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)", "needs IO[(Int, Map[String, String], String)]")) Str.concat("netBody ", Check.check("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)", "View.each needs A => View")) Str.concat("eachFun ", Check.check("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)")) else if (Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))") != "[]") Str.concat("netOk ", Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))")) else if (!rejects("def wrong[A](xs: List[Int], x: A): List[Int] = List.filter(xs, _ => x)", "arg type mismatch")) Str.concat("kitFilter ", Check.check("def wrong[A](xs: List[Int], x: A): List[Int] = List.filter(xs, _ => x)")) else if (Check.check("def right[B](xs: List[Int]): List[String] = List.map(xs, n => \"x\")") != "[]") Str.concat("kitMapB ", Check.check("def right[B](xs: List[Int]): List[String] = List.map(xs, n => \"x\")")) else "userTypeArgs-other" def dumpListFilter(): String = if (!rejects("def wrong(): List[Int] = [1, \"x\"]", "list element")) "listElem" else if (!rejects("def wrong(): List[Int] = List.concat([1], [\"x\"])", "arg type mismatch")) "listConcat" else if (!rejects("def wrong(): List[String] = List.map([1], x => x + 1)", "does not match declared")) "listMap" else if (Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") != "[]") Str.concat("filterOk ", Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)")) else if (!rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared")) Str.concat("filterMis ", Check.check("def wrong(): List[Int] = List.filter([\"a\"], x => true)")) else if (!rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare")) Str.concat("filterPred ", Check.check("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)")) else if (Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") != "[]") Str.concat("takeOk ", Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)")) else if (!rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared")) Str.concat("takeMis ", Check.check("def wrong(): List[Int] = List.take([\"a\"], 1)")) else "listFilter-other" From 1218830729d61e1c909518fe6622f8ea660b639f Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 17 Sep 2026 07:38:26 -0400 Subject: [PATCH 21/23] Add POSIX ERE capture and first-match replace. Str.capture returns the first match plus groups. Str.replaceMatch substitutes a literal string for that match. Str.matches stays a full-string test. --- crates/runtime/include/scuzz_rt.h | 7 ++ crates/runtime/src/runtime.c | 135 ++++++++++++++++++++++++++---- crates/runtime/tests/test_io.c | 38 +++++++++ docs/gaps.md | 2 +- docs/philosophy.md | 2 +- examples/compiler/src/Emit.scuzz | 16 ++-- examples/compiler/src/Kits.scuzz | 2 +- examples/tyck/src/Main.scuzz | 7 +- 8 files changed, 181 insertions(+), 28 deletions(-) diff --git a/crates/runtime/include/scuzz_rt.h b/crates/runtime/include/scuzz_rt.h index 075d28a0..f9c49ac8 100644 --- a/crates/runtime/include/scuzz_rt.h +++ b/crates/runtime/include/scuzz_rt.h @@ -193,6 +193,13 @@ typedef struct SzMap SzMap; SzList *sz_string_lines(const SzString *s); /* Split on non-overlapping `sep`. Empty `sep` copies `s` as one cell. */ SzList *sz_string_split(const SzString *s, const SzString *sep); +/* First match. Cell 0 is the full match. Later cells are groups. Empty + * when there is no match or the pattern is bad. Empty pattern is empty. */ +SzList *sz_string_capture(const SzString *s, const SzString *pat); +/* Replace the first match with a literal string. No backreferences. A + * miss, a bad pattern, or an empty pattern copies `s`. */ +SzString *sz_string_replace_match(const SzString *s, const SzString *pat, + const SzString *repl); /* Boxed i64 for IO[Int] */ void *sz_box_i64(int64_t n); diff --git a/crates/runtime/src/runtime.c b/crates/runtime/src/runtime.c index c49c66da..a3075506 100644 --- a/crates/runtime/src/runtime.c +++ b/crates/runtime/src/runtime.c @@ -1433,6 +1433,33 @@ int64_t sz_string_contains(const SzString *s, const SzString *needle) { return sz_string_index_of(s, needle) >= 0 ? 1 : 0; } +enum { SZ_RE_MAX_PAT = 1024, SZ_RE_MAX_MATCH = 32 }; + +static int sz_re_nul_ok(const SzString *s) { + return s && s->data && strlen(s->data) == s->len; +} + +/* 0 compiled, 1 empty pattern, -1 fail. Caller calls regfree when 0. */ +static int sz_re_compile(const SzString *pat, regex_t *re) { + if (!pat || !pat->data) + return -1; + if (pat->len > SZ_RE_MAX_PAT) + return -1; + if (strlen(pat->data) != pat->len) + return -1; + if (pat->len == 0) + return 1; + if (regcomp(re, pat->data, REG_EXTENDED) != 0) + return -1; + return 0; +} + +static SzString *sz_re_group(const char *text, const regmatch_t *m) { + if (!m || m->rm_so < 0) + return sz_string_from_bytes("", 0); + return sz_string_from_bytes(text + m->rm_so, (size_t)(m->rm_eo - m->rm_so)); +} + /* POSIX ERE. Full-string match on UTF-8 bytes. No capture. * A bad pattern, a NUL in the text, or a pattern over 1024 bytes is 0. * Empty pattern: match only the empty string. Do not call libc regex for @@ -1440,25 +1467,19 @@ int64_t sz_string_contains(const SzString *s, const SzString *needle) { int64_t sz_string_matches(const SzString *s, const SzString *pat) { regex_t re; regmatch_t m; - const char *text; - const char *p; + int crc; int rc; - if (!s || !s->data || !pat || !pat->data) - return 0; - if (pat->len > 1024) - return 0; - p = pat->data; - if (strlen(p) != pat->len) + crc = sz_re_compile(pat, &re); + if (crc == 1) + return s && s->len == 0 ? 1 : 0; + if (crc != 0) return 0; - text = s->data; - if (strlen(text) != s->len) + if (!sz_re_nul_ok(s)) { + regfree(&re); return 0; - if (pat->len == 0) - return s->len == 0 ? 1 : 0; - if (regcomp(&re, p, REG_EXTENDED) != 0) - return 0; - rc = regexec(&re, text, 1, &m, 0); + } + rc = regexec(&re, s->data, 1, &m, 0); regfree(&re); if (rc != 0) return 0; @@ -1467,6 +1488,90 @@ int64_t sz_string_matches(const SzString *s, const SzString *pat) { return 1; } +/* First match. Cell 0 is the full match. Later cells are groups. + * Empty list when there is no match or the pattern is bad. Empty pattern + * does not match. */ +SzList *sz_string_capture(const SzString *s, const SzString *pat) { + regex_t re; + regmatch_t m[SZ_RE_MAX_MATCH]; + size_t nmatch; + size_t i; + int crc; + SzList *acc = NULL; + + crc = sz_re_compile(pat, &re); + if (crc != 0) + return NULL; + if (!sz_re_nul_ok(s)) { + regfree(&re); + return NULL; + } + nmatch = re.re_nsub + 1; + if (nmatch > SZ_RE_MAX_MATCH) + nmatch = SZ_RE_MAX_MATCH; + if (regexec(&re, s->data, nmatch, m, 0) != 0) { + regfree(&re); + return NULL; + } + i = nmatch; + while (i > 0) { + SzString *g; + SzList *old; + i--; + g = sz_re_group(s->data, &m[i]); + old = acc; + acc = sz_list_cons(g, old); + sz_release(g); + sz_release(old); + } + regfree(&re); + return acc; +} + +/* Replace the first match with a literal string. No backreferences. + * A miss, a bad pattern, an empty pattern, or a NUL in the text copies s. */ +SzString *sz_string_replace_match(const SzString *s, const SzString *pat, + const SzString *repl) { + regex_t re; + regmatch_t m; + int crc; + size_t slen; + const char *text; + const char *rp; + size_t nrepl; + size_t out_len; + char *buf; + SzString *out; + + slen = s && s->data ? s->len : 0; + text = s && s->data ? s->data : ""; + nrepl = repl && repl->data ? repl->len : 0; + rp = repl && repl->data ? repl->data : ""; + crc = sz_re_compile(pat, &re); + if (crc != 0) + return sz_string_from_bytes(text, slen); + if (!sz_re_nul_ok(s)) { + regfree(&re); + return sz_string_from_bytes(text, slen); + } + if (regexec(&re, text, 1, &m, 0) != 0 || m.rm_so < 0) { + regfree(&re); + return sz_string_from_bytes(text, slen); + } + out_len = slen - (size_t)(m.rm_eo - m.rm_so) + nrepl; + buf = (char *)sz_alloc(out_len + 1); + memcpy(buf, text, (size_t)m.rm_so); + if (nrepl) + memcpy(buf + (size_t)m.rm_so, rp, nrepl); + memcpy(buf + (size_t)m.rm_so + nrepl, text + (size_t)m.rm_eo, + slen - (size_t)m.rm_eo); + buf[out_len] = '\0'; + out = sz_string_from_bytes(buf, out_len); + sz_free(buf); + regfree(&re); + return out; +} + int64_t sz_string_ends_with(const SzString *s, const SzString *suffix) { size_t slen; size_t n; diff --git a/crates/runtime/tests/test_io.c b/crates/runtime/tests/test_io.c index 36f8f096..6545e08d 100644 --- a/crates/runtime/tests/test_io.c +++ b/crates/runtime/tests/test_io.c @@ -6212,6 +6212,44 @@ int main(void) { 1); assert(sz_string_matches(sz_string_from_cstr("a"), sz_string_from_cstr("")) == 0); + { + SzList *cap = sz_string_capture(sz_string_from_cstr("abc123"), + sz_string_from_cstr("([a-z]+)([0-9]+)")); + assert(sz_list_len(cap) == 3); + assert(strcmp(sz_string_cstr((SzString *)cap->head), "abc123") == 0); + assert(strcmp(sz_string_cstr((SzString *)cap->tail->head), "abc") == 0); + assert(strcmp(sz_string_cstr((SzString *)cap->tail->tail->head), "123") == + 0); + sz_release(cap); + cap = sz_string_capture(sz_string_from_cstr("abc123x"), + sz_string_from_cstr("([a-z]+)([0-9]+)")); + assert(sz_list_len(cap) == 3); + sz_release(cap); + cap = sz_string_capture(sz_string_from_cstr("abc123"), + sz_string_from_cstr("^[a-z]+$")); + assert(cap == NULL); + cap = sz_string_capture(sz_string_from_cstr("a"), sz_string_from_cstr("[")); + assert(cap == NULL); + cap = sz_string_capture(sz_string_from_cstr("a"), sz_string_from_cstr("")); + assert(cap == NULL); + } + { + SzString *rep = sz_string_replace_match(sz_string_from_cstr("a1b"), + sz_string_from_cstr("[0-9]"), + sz_string_from_cstr("x")); + assert(strcmp(sz_string_cstr(rep), "axb") == 0); + sz_release(rep); + rep = sz_string_replace_match(sz_string_from_cstr("ab"), + sz_string_from_cstr("[0-9]"), + sz_string_from_cstr("x")); + assert(strcmp(sz_string_cstr(rep), "ab") == 0); + sz_release(rep); + rep = sz_string_replace_match(sz_string_from_cstr("a1b"), + sz_string_from_cstr("["), + sz_string_from_cstr("x")); + assert(strcmp(sz_string_cstr(rep), "a1b") == 0); + sz_release(rep); + } { SzString *h = sz_hash_sha256(sz_string_from_cstr("abc")); assert(strcmp(sz_string_cstr(h), diff --git a/docs/gaps.md b/docs/gaps.md index 0f815f49..10fa416c 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -47,7 +47,7 @@ Required for CLI, server, and desktop applications. Filesystem symbolic links, extended metadata preservation, and power-loss durability remain open. -`Map` / `Set` keys beyond `Int` or `String`. `scuzz eval`. Time parse and zones. Regex capture and replace. Generators. Drive `==` wrap on UI. OS threads. HTTPS serve with app cert and key files. +`Map` / `Set` keys beyond `Int` or `String`. `scuzz eval`. Time parse and zones. Generators. Drive `==` wrap on UI. OS threads. HTTPS serve with app cert and key files. ### Later diff --git a/docs/philosophy.md b/docs/philosophy.md index 33957ca6..5fb567a4 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -86,7 +86,7 @@ No vendored Skia tree. Thin `sk_capi` (measure + draw). **Default UI backend** i ### IO and impurity -One failure channel: `SzError` on `IO[T]`. Typed `E` on `IO` without environment `R`. Do not add `ZIO[R, E, A]`. Blessed kits only. No app-level `IO.delay`. No user FFI, `extern`, or plugins. Determinism and effect capture are not settled. Cooperative single-threaded fibers are the scheduler. Simulation is hermetic. No live sockets under sim. Persistent HTTP servers wait for new requests until cancellation in live and simulation runtimes. One Net API uses shared request checks, timeline events, and simulation dispatch. CLI and server HTTP use the HTTP/1.0 transport with OpenSSL. iOS and macOS GUI HTTP use URLSession and platform certificate trust. GUI clients verify loopback certificates. Requests park fibers and cancel through IO finalizers. GUI transport preserves status responses and does not follow redirects. A response is `(Int, Map[String, String], String)`. Serve binds `0.0.0.0` and `::`. `Net.serveTls` and `Net.serveOnceTls` terminate TLS with a process cert. The CLI and server loopback `https://` client does not verify that cert. Do not expose POSIX sockets. Do not add an app transport API. `Clock.iso8601` formats UTC from epoch milliseconds. No general time parser. No time zone kit. `Str.matches` is POSIX ERE full-string match on UTF-8 bytes. No capture. No replace. `Hash.sha256` returns lowercase hex of the SHA-256 of UTF-8 bytes. Software SHA-256. No OpenSSL. Hash.hmacSha256 computes HMAC-SHA-256. Hash.constantTimeEqual compares equal-length byte strings without an early exit. Length is public. No other digests. `Hex.encode` returns lowercase hex of UTF-8 bytes. `Hex.decode` reverses that encoding. Odd length or a bad digit yields the empty string. `Base64.encode` returns RFC 4648 of UTF-8 bytes. `Base64.decode` reverses that encoding. Bad length, digit, or pad yields the empty string. No URL-safe alphabet. `Uuid.v4` returns an RFC 4122 version-4 UUID as lowercase hex with hyphens. It uses the blessed Random stream. No parse. No other versions. `Bytes.fromStr` copies UTF-8 bytes. `Bytes.len` is the byte count. No Fs or Net Bytes. Kits: run `scuzz docs kits`. A panic must print a Scuzz file and line. +One failure channel: `SzError` on `IO[T]`. Typed `E` on `IO` without environment `R`. Do not add `ZIO[R, E, A]`. Blessed kits only. No app-level `IO.delay`. No user FFI, `extern`, or plugins. Determinism and effect capture are not settled. Cooperative single-threaded fibers are the scheduler. Simulation is hermetic. No live sockets under sim. Persistent HTTP servers wait for new requests until cancellation in live and simulation runtimes. One Net API uses shared request checks, timeline events, and simulation dispatch. CLI and server HTTP use the HTTP/1.0 transport with OpenSSL. iOS and macOS GUI HTTP use URLSession and platform certificate trust. GUI clients verify loopback certificates. Requests park fibers and cancel through IO finalizers. GUI transport preserves status responses and does not follow redirects. A response is `(Int, Map[String, String], String)`. Serve binds `0.0.0.0` and `::`. `Net.serveTls` and `Net.serveOnceTls` terminate TLS with a process cert. The CLI and server loopback `https://` client does not verify that cert. Do not expose POSIX sockets. Do not add an app transport API. `Clock.iso8601` formats UTC from epoch milliseconds. No general time parser. No time zone kit. `Str.matches` is POSIX ERE full-string match on UTF-8 bytes. `Str.capture` returns the first match as a list: the full match, then each group. An empty list means no match or a bad pattern. `Str.replaceMatch` replaces the first match with a literal string. It does not expand backreferences. An empty pattern copies the text. `Hash.sha256` returns lowercase hex of the SHA-256 of UTF-8 bytes. Software SHA-256. No OpenSSL. Hash.hmacSha256 computes HMAC-SHA-256. Hash.constantTimeEqual compares equal-length byte strings without an early exit. Length is public. No other digests. `Hex.encode` returns lowercase hex of UTF-8 bytes. `Hex.decode` reverses that encoding. Odd length or a bad digit yields the empty string. `Base64.encode` returns RFC 4648 of UTF-8 bytes. `Base64.decode` reverses that encoding. Bad length, digit, or pad yields the empty string. No URL-safe alphabet. `Uuid.v4` returns an RFC 4122 version-4 UUID as lowercase hex with hyphens. It uses the blessed Random stream. No parse. No other versions. `Bytes.fromStr` copies UTF-8 bytes. `Bytes.len` is the byte count. No Fs or Net Bytes. Kits: run `scuzz docs kits`. A panic must print a Scuzz file and line. `Fs.write` replaces a regular file in one operation. The live runtime writes a temporary file in the same directory, checks write and close errors, then renames it over the destination. An error before replacement preserves the destination. New files use mode 0600. Replacement keeps the existing access permission bits. It does not preserve other inode metadata. The destination cannot be a symbolic link or a special file. This is atomic visibility, not a power-loss durability guarantee. Simulation applies the same complete-content replacement. diff --git a/examples/compiler/src/Emit.scuzz b/examples/compiler/src/Emit.scuzz index 665b5d98..852ceeee 100644 --- a/examples/compiler/src/Emit.scuzz +++ b/examples/compiler/src/Emit.scuzz @@ -1839,7 +1839,7 @@ def emitJsonNull(code: String, prefix: String): Slot = Slot(join(code, emitAdtNew(prefix, 0, "null")), pct(prefix, "adt"), true) def emitCall3b(f: String, code: String, vals: List[String], owns: List[Bool], prefix: String, defs: Ftab, args: List[Expr], ens: List[En], ps: List[Param], mod: String): Slot = - if (f == "Scenario.context") emitClock("sz_scenario_context", code, prefix) else if (isUiKit(f)) emitUiKit(f, code, vals, owns, prefix) else if (f == "List.setAt") emitListSetAt(code, vals, owns, prefix) else if (f == "Str.fromInt") emitFromInt(code, vals, prefix, ps, owns, args) else if (f == "Str.fromBool") emitFromBool(code, vals, prefix) else if (f == "Str.concat") emitConcat(code, vals, owns, prefix) else if (f == "Str.len") emitStrLen(code, vals, owns, prefix) else if (f == "Str.byteLen") emitByteLen(code, vals, owns, prefix) else if (f == "Str.charAt") emitCharAt(code, vals, owns, prefix) else if (f == "Str.slice") emitSlice(code, vals, owns, prefix) else if (f == "Str.byteSlice") emitByteSlice(code, vals, owns, prefix) else if (f == "Str.toInt") emitToInt(code, vals, owns, prefix) else if (f == "Str.repeat") emitRepeat(code, vals, owns, prefix) else if (f == "Str.startsWith") emitStarts(code, vals, owns, prefix) else if (f == "Str.eq") emitStrEq(code, vals, owns, prefix) else if (f == "List.len") emitListLen(code, vals, owns, prefix) else if (f == "List.concat") emitListCat(code, vals, owns, prefix) else if (f == "List.reverse") emitListRev(code, vals, owns, prefix) else if (f == "List.head") emitListHead(code, vals, owns, prefix) else if (f == "List.tail") emitListTail(code, vals, owns, prefix) else if (f == "List.isEmpty") emitListEmpty(code, vals, owns, prefix) else if (f == "List.cons") emitConsCall(code, vals, owns, prefix, args, ps, defs) else if (f == "List.at") emitAt(code, vals, owns, prefix, args, ps, defs, ens, mod) else if (f == "List.join") emitListJoin(code, vals, owns, prefix) else if (kitUnaryPtr(f) != "") emitKitUnaryPtr(kitUnaryPtr(f), code, vals, owns, prefix) else if (kitUnaryI64(f) != "") emitKitUnaryI64(kitUnaryI64(f), code, vals, owns, prefix) else if (kitPtrI64(f) != "") emitKitPtrI64(kitPtrI64(f), code, vals, owns, prefix) else if (kitBinaryPtr(f) != "") emitKitBinaryPtr(kitBinaryPtr(f), code, vals, owns, prefix) else if (kitBinaryI64(f) != "") emitKitBinaryI64(kitBinaryI64(f), code, vals, owns, prefix) else if (f == "Stream.zipAll") emitZipAllKit("sz_stream_zip_all", code, vals, owns, prefix) else if (f == "List.zipAll") emitZipAllKit("sz_list_zip_all", code, vals, owns, prefix) else if (f == "Str.replace") emitStrReplace(code, vals, owns, prefix) else if (f == "Str.padLeft") emitStrPad("sz_string_pad_left", code, vals, owns, prefix) else if (f == "Str.padRight") emitStrPad("sz_string_pad_right", code, vals, owns, prefix) else if (f == "List.empty") emitListNil(code, prefix) else if (f == "Map.empty" || f == "Set.empty") emitMapEmpty(code, prefix) else if (f == "Map.set") emitMapSet(code, vals, owns, prefix, isI64Ty(exprRetTyEns(List.at(args, 2), defs, ens, ps, mod))) else if (f == "Set.add") emitSetAdd(code, vals, owns, prefix) else if (f == "Map.getOrElse") emitMapGetOr(code, vals, owns, prefix) else if (f == "Clock.iso8601") emitClockIso(code, vals, prefix, ps, owns, args) else if (f == "Clock.monotonic") emitClock("sz_clock_monotonic", code, prefix) else if (f == "Clock.realTime") emitClock("sz_clock_real_time", code, prefix) else if (f == "Uuid.v4") emitClock("sz_uuid_v4", code, prefix) else if (f == "Queue.unbounded") emitClock("sz_queue_unbounded", code, prefix) else if (f == "Deferred.empty") emitClock("sz_deferred_empty", code, prefix) else if (f == "Fs.mkdirs") emitFsMkdirs(code, vals, owns, prefix) else if (f == "Builder.empty") emitBldNew(code, prefix) else if (f == "Builder.append") emitBldApp(code, vals, owns, prefix) else if (f == "Builder.result") emitBldRes(code, vals, owns, prefix) else if (f == "Fs.read") emitFsRead(code, vals, owns, prefix) else if (f == "Fs.write") emitFsWrite(code, vals, owns, prefix) else if (f == "Fs.list") emitFsList(code, vals, owns, prefix) else if (f == "Fs.canonicalize") emitFsReadRt("sz_fs_canonicalize", code, vals, owns, prefix) else if (f == "Fs.exists") emitFsReadRt("sz_fs_exists", code, vals, owns, prefix) else if (f == "Fs.walk") emitFsReadRt("sz_fs_walk", code, vals, owns, prefix) else if (f == "Fs.delete") emitFsReadRt("sz_fs_delete", code, vals, owns, prefix) else if (f == "Fs.rename") emitFsWriteRt("sz_fs_rename", code, vals, owns, prefix) else if (f == "Net.httpGet" || f == "Net.httpDelete" || f == "Net.httpHead") emitFsWriteRt(netHttpUnaryRt(f), code, vals, owns, prefix) else if (f == "Net.httpPost") emitHttpBody("sz_net_http_post", code, vals, owns, prefix) else if (f == "Net.httpPut") emitHttpBody("sz_net_http_put", code, vals, owns, prefix) else if (f == "Net.httpPatch") emitHttpBody("sz_net_http_patch", code, vals, owns, prefix) else if (f == "Net.tcpListen") emitSysI64("sz_net_tcp_listen", code, vals, prefix) else if (f == "Net.udpBind") emitSysI64("sz_net_udp_bind", code, vals, prefix) else if (f == "Net.tcpConnect") emitNetPtrI64("sz_net_tcp_connect", code, vals, owns, prefix) else if (f == "Net.tcpRead") emitNetPtrI64("sz_net_tcp_read", code, vals, owns, prefix) else if (f == "Net.udpRecv") emitNetPtrI64("sz_net_udp_recv", code, vals, owns, prefix) else if (f == "Net.tcpWrite") emitFsWriteRt("sz_net_tcp_write", code, vals, owns, prefix) else if (f == "Net.udpSend") emitNetUdpSend(code, vals, owns, prefix) else if (f == "Net.tcpAccept") emitFsReadRt("sz_net_tcp_accept", code, vals, owns, prefix) else if (f == "Net.tcpClose") emitFsReadRt("sz_net_tcp_close", code, vals, owns, prefix) else if (f == "Net.udpClose") emitFsReadRt("sz_net_udp_close", code, vals, owns, prefix) else if (f == "Random.nextInt") emitSysI64("sz_random_next_int", code, vals, prefix) else if (f == "Sys.getenv") emitFsReadRt("sz_sys_getenv", code, vals, owns, prefix) else if (f == "Sys.write") emitFsReadRt("sz_sys_write", code, vals, owns, prefix) else if (f == "Sys.exec") emitFsReadRt("sz_sys_exec", code, vals, owns, prefix) else if (f == "Sys.spawn") emitFsReadRt("sz_sys_spawn", code, vals, owns, prefix) else if (f == "Sys.kill") emitSysI64("sz_sys_kill", code, vals, prefix) else if (f == "Sys.alive") emitSysI64("sz_sys_alive", code, vals, prefix) else if (f == "Sys.childClose") emitSysI64("sz_sys_child_close", code, vals, prefix) else if (f == "Sys.childRead") emitSysI64I64("sz_sys_child_read", code, vals, prefix) else if (f == "Sys.childWrite") emitSysI64Ptr("sz_sys_child_write", code, vals, owns, prefix) else if (f == "Impurity.runKit") emitClock("sz_impurity_run_kit", code, prefix) else if (f == "Sys.args") emitSysArgs(code, prefix) else if (f == "Sys.read") emitSysI64("sz_sys_read", code, vals, prefix) else if (f == "Sys.readLine") emitClock("sz_sys_read_line", code, prefix) else if (f == "IO.pure") emitIoPure(code, vals, owns, prefix, ps) else if (f == "List.range") emitListRange(code, vals, prefix) else if (f == "List.fill") emitListFill(code, vals, owns, prefix) else if (f == "List.getOrElse") emitListGetOr(code, vals, owns, prefix) else if (f == "List.padTo") emitListPadTo(code, vals, owns, prefix) else if (f == "List.max") emitListExtreme("sz_list_max", code, vals, owns, prefix, args, ps, defs) else if (f == "List.min") emitListExtreme("sz_list_min", code, vals, owns, prefix, args, ps, defs) else if (f == "List.toSet") emitListToSet(code, vals, owns, prefix, args, ps) else if (f == "List.slice") emitListSlice(code, vals, owns, prefix) else if (f == "List.isDefinedAt") emitKitPtrI64I64("sz_list_is_defined_at", code, vals, owns, prefix) else if (f == "List.lengthCompare") emitKitPtrI64I64("sz_list_length_compare", code, vals, owns, prefix) else if (f == "Map.isEmpty" || f == "Set.isEmpty") emitMapSizePred(code, vals, owns, prefix, "eq") else if (f == "Map.nonEmpty" || f == "Set.nonEmpty") emitMapSizePred(code, vals, owns, prefix, "ne") else if (f == "Float.toInt") emitFloatToInt(code, vals, owns, prefix, ps) else if (f == "Float.fromInt") emitFloatFromInt(code, vals, prefix) else if (f == "Oracle.sumTo") emitOracleSumTo(code, vals, prefix) else if (hasEn(ens, f) || isCtorName(f)) emitRec(f, code, vals, owns, prefix, ens, args, ps, defs, mod) else if (isQualCtor(f, ens)) emitPayloadCtorN(qualEn(f), qualName(f), (code, vals, owns), args, prefix, ens, ps, defs, mod) else emitUser(f, code, vals, owns, prefix, defs, mod) + if (f == "Scenario.context") emitClock("sz_scenario_context", code, prefix) else if (isUiKit(f)) emitUiKit(f, code, vals, owns, prefix) else if (f == "List.setAt") emitListSetAt(code, vals, owns, prefix) else if (f == "Str.fromInt") emitFromInt(code, vals, prefix, ps, owns, args) else if (f == "Str.fromBool") emitFromBool(code, vals, prefix) else if (f == "Str.concat") emitConcat(code, vals, owns, prefix) else if (f == "Str.len") emitStrLen(code, vals, owns, prefix) else if (f == "Str.byteLen") emitByteLen(code, vals, owns, prefix) else if (f == "Str.charAt") emitCharAt(code, vals, owns, prefix) else if (f == "Str.slice") emitSlice(code, vals, owns, prefix) else if (f == "Str.byteSlice") emitByteSlice(code, vals, owns, prefix) else if (f == "Str.toInt") emitToInt(code, vals, owns, prefix) else if (f == "Str.repeat") emitRepeat(code, vals, owns, prefix) else if (f == "Str.startsWith") emitStarts(code, vals, owns, prefix) else if (f == "Str.eq") emitStrEq(code, vals, owns, prefix) else if (f == "List.len") emitListLen(code, vals, owns, prefix) else if (f == "List.concat") emitListCat(code, vals, owns, prefix) else if (f == "List.reverse") emitListRev(code, vals, owns, prefix) else if (f == "List.head") emitListHead(code, vals, owns, prefix) else if (f == "List.tail") emitListTail(code, vals, owns, prefix) else if (f == "List.isEmpty") emitListEmpty(code, vals, owns, prefix) else if (f == "List.cons") emitConsCall(code, vals, owns, prefix, args, ps, defs) else if (f == "List.at") emitAt(code, vals, owns, prefix, args, ps, defs, ens, mod) else if (f == "List.join") emitListJoin(code, vals, owns, prefix) else if (kitUnaryPtr(f) != "") emitKitUnaryPtr(kitUnaryPtr(f), code, vals, owns, prefix) else if (kitUnaryI64(f) != "") emitKitUnaryI64(kitUnaryI64(f), code, vals, owns, prefix) else if (kitPtrI64(f) != "") emitKitPtrI64(kitPtrI64(f), code, vals, owns, prefix) else if (kitBinaryPtr(f) != "") emitKitBinaryPtr(kitBinaryPtr(f), code, vals, owns, prefix) else if (kitBinaryI64(f) != "") emitKitBinaryI64(kitBinaryI64(f), code, vals, owns, prefix) else if (f == "Stream.zipAll") emitZipAllKit("sz_stream_zip_all", code, vals, owns, prefix) else if (f == "List.zipAll") emitZipAllKit("sz_list_zip_all", code, vals, owns, prefix) else if (f == "Str.replace") emitStrReplace("sz_string_replace", code, vals, owns, prefix) else if (f == "Str.replaceMatch") emitStrReplace("sz_string_replace_match", code, vals, owns, prefix) else if (f == "Str.padLeft") emitStrPad("sz_string_pad_left", code, vals, owns, prefix) else if (f == "Str.padRight") emitStrPad("sz_string_pad_right", code, vals, owns, prefix) else if (f == "List.empty") emitListNil(code, prefix) else if (f == "Map.empty" || f == "Set.empty") emitMapEmpty(code, prefix) else if (f == "Map.set") emitMapSet(code, vals, owns, prefix, isI64Ty(exprRetTyEns(List.at(args, 2), defs, ens, ps, mod))) else if (f == "Set.add") emitSetAdd(code, vals, owns, prefix) else if (f == "Map.getOrElse") emitMapGetOr(code, vals, owns, prefix) else if (f == "Clock.iso8601") emitClockIso(code, vals, prefix, ps, owns, args) else if (f == "Clock.monotonic") emitClock("sz_clock_monotonic", code, prefix) else if (f == "Clock.realTime") emitClock("sz_clock_real_time", code, prefix) else if (f == "Uuid.v4") emitClock("sz_uuid_v4", code, prefix) else if (f == "Queue.unbounded") emitClock("sz_queue_unbounded", code, prefix) else if (f == "Deferred.empty") emitClock("sz_deferred_empty", code, prefix) else if (f == "Fs.mkdirs") emitFsMkdirs(code, vals, owns, prefix) else if (f == "Builder.empty") emitBldNew(code, prefix) else if (f == "Builder.append") emitBldApp(code, vals, owns, prefix) else if (f == "Builder.result") emitBldRes(code, vals, owns, prefix) else if (f == "Fs.read") emitFsRead(code, vals, owns, prefix) else if (f == "Fs.write") emitFsWrite(code, vals, owns, prefix) else if (f == "Fs.list") emitFsList(code, vals, owns, prefix) else if (f == "Fs.canonicalize") emitFsReadRt("sz_fs_canonicalize", code, vals, owns, prefix) else if (f == "Fs.exists") emitFsReadRt("sz_fs_exists", code, vals, owns, prefix) else if (f == "Fs.walk") emitFsReadRt("sz_fs_walk", code, vals, owns, prefix) else if (f == "Fs.delete") emitFsReadRt("sz_fs_delete", code, vals, owns, prefix) else if (f == "Fs.rename") emitFsWriteRt("sz_fs_rename", code, vals, owns, prefix) else if (f == "Net.httpGet" || f == "Net.httpDelete" || f == "Net.httpHead") emitFsWriteRt(netHttpUnaryRt(f), code, vals, owns, prefix) else if (f == "Net.httpPost") emitHttpBody("sz_net_http_post", code, vals, owns, prefix) else if (f == "Net.httpPut") emitHttpBody("sz_net_http_put", code, vals, owns, prefix) else if (f == "Net.httpPatch") emitHttpBody("sz_net_http_patch", code, vals, owns, prefix) else if (f == "Net.tcpListen") emitSysI64("sz_net_tcp_listen", code, vals, prefix) else if (f == "Net.udpBind") emitSysI64("sz_net_udp_bind", code, vals, prefix) else if (f == "Net.tcpConnect") emitNetPtrI64("sz_net_tcp_connect", code, vals, owns, prefix) else if (f == "Net.tcpRead") emitNetPtrI64("sz_net_tcp_read", code, vals, owns, prefix) else if (f == "Net.udpRecv") emitNetPtrI64("sz_net_udp_recv", code, vals, owns, prefix) else if (f == "Net.tcpWrite") emitFsWriteRt("sz_net_tcp_write", code, vals, owns, prefix) else if (f == "Net.udpSend") emitNetUdpSend(code, vals, owns, prefix) else if (f == "Net.tcpAccept") emitFsReadRt("sz_net_tcp_accept", code, vals, owns, prefix) else if (f == "Net.tcpClose") emitFsReadRt("sz_net_tcp_close", code, vals, owns, prefix) else if (f == "Net.udpClose") emitFsReadRt("sz_net_udp_close", code, vals, owns, prefix) else if (f == "Random.nextInt") emitSysI64("sz_random_next_int", code, vals, prefix) else if (f == "Sys.getenv") emitFsReadRt("sz_sys_getenv", code, vals, owns, prefix) else if (f == "Sys.write") emitFsReadRt("sz_sys_write", code, vals, owns, prefix) else if (f == "Sys.exec") emitFsReadRt("sz_sys_exec", code, vals, owns, prefix) else if (f == "Sys.spawn") emitFsReadRt("sz_sys_spawn", code, vals, owns, prefix) else if (f == "Sys.kill") emitSysI64("sz_sys_kill", code, vals, prefix) else if (f == "Sys.alive") emitSysI64("sz_sys_alive", code, vals, prefix) else if (f == "Sys.childClose") emitSysI64("sz_sys_child_close", code, vals, prefix) else if (f == "Sys.childRead") emitSysI64I64("sz_sys_child_read", code, vals, prefix) else if (f == "Sys.childWrite") emitSysI64Ptr("sz_sys_child_write", code, vals, owns, prefix) else if (f == "Impurity.runKit") emitClock("sz_impurity_run_kit", code, prefix) else if (f == "Sys.args") emitSysArgs(code, prefix) else if (f == "Sys.read") emitSysI64("sz_sys_read", code, vals, prefix) else if (f == "Sys.readLine") emitClock("sz_sys_read_line", code, prefix) else if (f == "IO.pure") emitIoPure(code, vals, owns, prefix, ps) else if (f == "List.range") emitListRange(code, vals, prefix) else if (f == "List.fill") emitListFill(code, vals, owns, prefix) else if (f == "List.getOrElse") emitListGetOr(code, vals, owns, prefix) else if (f == "List.padTo") emitListPadTo(code, vals, owns, prefix) else if (f == "List.max") emitListExtreme("sz_list_max", code, vals, owns, prefix, args, ps, defs) else if (f == "List.min") emitListExtreme("sz_list_min", code, vals, owns, prefix, args, ps, defs) else if (f == "List.toSet") emitListToSet(code, vals, owns, prefix, args, ps) else if (f == "List.slice") emitListSlice(code, vals, owns, prefix) else if (f == "List.isDefinedAt") emitKitPtrI64I64("sz_list_is_defined_at", code, vals, owns, prefix) else if (f == "List.lengthCompare") emitKitPtrI64I64("sz_list_length_compare", code, vals, owns, prefix) else if (f == "Map.isEmpty" || f == "Set.isEmpty") emitMapSizePred(code, vals, owns, prefix, "eq") else if (f == "Map.nonEmpty" || f == "Set.nonEmpty") emitMapSizePred(code, vals, owns, prefix, "ne") else if (f == "Float.toInt") emitFloatToInt(code, vals, owns, prefix, ps) else if (f == "Float.fromInt") emitFloatFromInt(code, vals, prefix) else if (f == "Oracle.sumTo") emitOracleSumTo(code, vals, prefix) else if (hasEn(ens, f) || isCtorName(f)) emitRec(f, code, vals, owns, prefix, ens, args, ps, defs, mod) else if (isQualCtor(f, ens)) emitPayloadCtorN(qualEn(f), qualName(f), (code, vals, owns), args, prefix, ens, ps, defs, mod) else emitUser(f, code, vals, owns, prefix, defs, mod) def emitUiKit(f: String, code: String, vals: List[String], owns: List[Bool], prefix: String): Slot = if (f == "Color.rgb") emitColorRgb(code, vals, prefix) else if (f == "Color.rgba") emitColorRgba(code, vals, prefix) else if (Str.startsWith(f, "Theme.") || Str.startsWith(f, "Icon.")) emitTheme(f, code, prefix) else if (f == "Ui.run") emitUiRun(code, vals, owns, prefix) else if (Str.startsWith(f, "Ui.")) emitUiSess(f, code, vals, owns, prefix) else if (Str.startsWith(f, "Property.")) emitPropKit(f, code, vals, owns, prefix) else emitViewKit(f, code, vals, owns, prefix) @@ -2137,11 +2137,11 @@ def emitListPadTo(code: String, vals: List[String], owns: List[Bool], prefix: St def emitListPadToCall(vals: List[String], prefix: String): String = line(Str.concat(tmp(prefix, "v"), Str.concat("call ptr @sz_list_pad_to(ptr ", Str.concat(arg0(vals), Str.concat(", i64 ", Str.concat(arg1(vals), Str.concat(", ptr ", Str.concat(arg2(vals), ")")))))))) -def emitStrReplace(code: String, vals: List[String], owns: List[Bool], prefix: String): Slot = - Slot(join(code, join(emitStrReplaceCall(vals, prefix), join(if (headOwn(owns)) relPtr(arg0(vals)) else "", join(if (headOwn(tailOwn(owns))) relPtr(arg1(vals)) else "", if (headOwn(tailOwn(tailOwn(owns)))) relPtr(arg2(vals)) else "")))), pct(prefix, "v"), true) +def emitStrReplace(rt: String, code: String, vals: List[String], owns: List[Bool], prefix: String): Slot = + Slot(join(code, join(emitStrReplaceCall(rt, vals, prefix), join(if (headOwn(owns)) relPtr(arg0(vals)) else "", join(if (headOwn(tailOwn(owns))) relPtr(arg1(vals)) else "", if (headOwn(tailOwn(tailOwn(owns)))) relPtr(arg2(vals)) else "")))), pct(prefix, "v"), true) -def emitStrReplaceCall(vals: List[String], prefix: String): String = - line(Str.concat(tmp(prefix, "v"), Str.concat("call ptr @sz_string_replace(ptr ", Str.concat(arg0(vals), Str.concat(", ptr ", Str.concat(arg1(vals), Str.concat(", ptr ", Str.concat(arg2(vals), ")")))))))) +def emitStrReplaceCall(rt: String, vals: List[String], prefix: String): String = + line(Str.concat(tmp(prefix, "v"), Str.concat("call ptr @", Str.concat(rt, Str.concat("(ptr ", Str.concat(arg0(vals), Str.concat(", ptr ", Str.concat(arg1(vals), Str.concat(", ptr ", Str.concat(arg2(vals), ")")))))))))) def emitStrPad(rt: String, code: String, vals: List[String], owns: List[Bool], prefix: String): Slot = Slot(join(code, join(emitStrPadCall(rt, vals, prefix), join(if (headOwn(owns)) relPtr(arg0(vals)) else "", if (headOwn(tailOwn(tailOwn(owns)))) relPtr(arg2(vals)) else ""))), pct(prefix, "v"), true) @@ -2687,7 +2687,7 @@ def kitBinaryPtr(f: String): String = if (f == "Hash.hmacSha256") "sz_hash_hmac_sha256" else if (f == "Net.nextLink") "sz_net_next_link" else if (f == "Json.get") "sz_json_get" else if (f == "Json.merge") "sz_json_merge" else if (f == "Json.remove") "sz_json_remove" else if (f == "Json.append") "sz_json_append" else if (f == "Json.prepend") "sz_json_prepend" else if (f == "Set.union") "sz_set_union" else if (f == "Set.intersect") "sz_set_intersect" else if (f == "Set.diff") "sz_set_diff" else if (f == "Map.union") "sz_map_union" else if (f == "Map.intersect") "sz_map_intersect" else if (f == "Map.diff") "sz_map_diff" else if (f == "Map.remove" || f == "Set.remove") "sz_map_remove" else if (f == "Map.get") "sz_map_get" else kitBinaryPtrB(f) def kitBinaryPtrB(f: String): String = - if (f == "IO.both") "sz_io_both" else if (f == "IO.race") "sz_io_race" else if (f == "IO.ensure") "sz_io_ensure" else if (f == "Ref.set") "sz_ref_set" else if (f == "Queue.offer") "sz_queue_offer" else if (f == "Deferred.complete") "sz_deferred_complete" else if (f == "Deferred.fail") "sz_deferred_fail" else if (f == "Stream.concat") "sz_stream_concat" else if (f == "Stream.zip") "sz_stream_zip" else if (f == "Stream.interleave") "sz_stream_interleave" else if (f == "Stream.orElse") "sz_stream_or_else" else if (f == "Stream.intersperse") "sz_stream_intersperse" else if (f == "List.interleave") "sz_list_interleave" else if (f == "List.append") "sz_list_append" else if (f == "List.zip") "sz_list_zip" else if (f == "List.intersperse") "sz_list_intersperse" else if (f == "List.diff") "sz_list_diff" else if (f == "List.intersect") "sz_list_intersect" else if (f == "Str.split") "sz_string_split" else if (f == "Str.stripPrefix") "sz_string_strip_prefix" else if (f == "Str.stripSuffix") "sz_string_strip_suffix" else if (f == "Fs.join") "sz_fs_join" else "" + if (f == "IO.both") "sz_io_both" else if (f == "IO.race") "sz_io_race" else if (f == "IO.ensure") "sz_io_ensure" else if (f == "Ref.set") "sz_ref_set" else if (f == "Queue.offer") "sz_queue_offer" else if (f == "Deferred.complete") "sz_deferred_complete" else if (f == "Deferred.fail") "sz_deferred_fail" else if (f == "Stream.concat") "sz_stream_concat" else if (f == "Stream.zip") "sz_stream_zip" else if (f == "Stream.interleave") "sz_stream_interleave" else if (f == "Stream.orElse") "sz_stream_or_else" else if (f == "Stream.intersperse") "sz_stream_intersperse" else if (f == "List.interleave") "sz_list_interleave" else if (f == "List.append") "sz_list_append" else if (f == "List.zip") "sz_list_zip" else if (f == "List.intersperse") "sz_list_intersperse" else if (f == "List.diff") "sz_list_diff" else if (f == "List.intersect") "sz_list_intersect" else if (f == "Str.split") "sz_string_split" else if (f == "Str.capture") "sz_string_capture" else if (f == "Str.stripPrefix") "sz_string_strip_prefix" else if (f == "Str.stripSuffix") "sz_string_strip_suffix" else if (f == "Fs.join") "sz_fs_join" else "" def kitBinaryI64(f: String): String = if (f == "Hash.constantTimeEqual") "sz_hash_constant_time_equal" else if (f == "Map.contains" || f == "Set.contains") "sz_map_contains" else if (f == "Json.has") "sz_json_has" else if (f == "Str.contains") "sz_string_contains" else if (f == "Str.matches") "sz_string_matches" else if (f == "Str.endsWith") "sz_string_ends_with" else if (f == "Str.indexOf") "sz_string_uindex_of" else if (f == "Str.lastIndexOf") "sz_string_ulast_index_of" else if (f == "Set.isSubset") "sz_set_is_subset" else if (f == "Set.isDisjoint") "sz_set_is_disjoint" else if (f == "List.contains") "sz_list_contains" else if (f == "List.indexOf") "sz_list_index_of" else if (f == "List.lastIndexOf") "sz_list_last_index_of" else if (f == "List.indexOfSlice") "sz_list_index_of_slice" else if (f == "List.lastIndexOfSlice") "sz_list_last_index_of_slice" else "" @@ -3591,7 +3591,7 @@ def emitCtorOrQual(en: String, name: String, args: List[Expr], prefix: String, s if (isEmitKit(Str.concat(en, Str.concat(".", name)))) emitCall(Str.concat(en, Str.concat(".", name)), args, prefix, strs, defs, ens, ps, loc, 0) else if (hasEn(ens, en)) emitPayloadCtor(en, name, args, prefix, strs, defs, ens, ps, loc) else emitQual(en, name, args, prefix, strs, defs, ens, ps, loc) def isEmitKit(f: String): Bool = - isUiKit(f) || f == "List.setAt" || f == "List.join" || kitUnaryPtr(f) != "" || kitUnaryI64(f) != "" || kitPtrI64(f) != "" || kitBinaryPtr(f) != "" || kitBinaryI64(f) != "" || isJsonExtra(f) || isRtExtra(f) || isMapKit(f) || f == "Map.empty" || f == "Set.empty" || f == "List.empty" || f == "Str.replace" || f == "Str.padLeft" || f == "Str.padRight" || f == "Map.set" || f == "Set.add" || f == "Map.getOrElse" || f == "Clock.iso8601" || f == "Clock.monotonic" || f == "Clock.realTime" || f == "Fs.mkdirs" || f == "Fs.canonicalize" || f == "Fs.exists" || f == "Fs.walk" || f == "Fs.delete" || f == "List.len" || f == "List.concat" || f == "List.reverse" || f == "List.head" || f == "List.tail" || f == "List.isEmpty" || f == "List.cons" || f == "List.at" || f == "Str.fromInt" || f == "Str.fromBool" || f == "Str.concat" || f == "Str.len" || f == "Str.byteLen" || f == "Str.byteSlice" || f == "Fs.read" || f == "Fs.write" || f == "Fs.list" || f == "IO.pure" || f == "Sys.getenv" || f == "Impurity.runKit" || f == "Net.serveOnce" || f == "Net.serve" || f == "Net.serveOnceTls" || f == "Net.serveTls" || isNetEmit(f) || f == "Queue.unbounded" || f == "Deferred.empty" || f == "Float.toInt" || f == "Float.fromInt" || f == "Map.isEmpty" || f == "Set.isEmpty" || f == "Map.nonEmpty" || f == "Set.nonEmpty" || f == "List.range" || f == "List.fill" || f == "List.getOrElse" || f == "List.padTo" || f == "List.max" || f == "List.min" || f == "List.sort" || f == "List.toSet" || f == "List.slice" || f == "List.isDefinedAt" || f == "List.lengthCompare" || f == "List.tabulate" || f == "List.segmentLength" || isTlKit(f) || f == "Net.retryAfterMillis" || f == "Oracle.sumTo" || f == "Fs.rename" || f == "Random.nextInt" || f == "Uuid.v4" + isUiKit(f) || f == "List.setAt" || f == "List.join" || kitUnaryPtr(f) != "" || kitUnaryI64(f) != "" || kitPtrI64(f) != "" || kitBinaryPtr(f) != "" || kitBinaryI64(f) != "" || isJsonExtra(f) || isRtExtra(f) || isMapKit(f) || f == "Map.empty" || f == "Set.empty" || f == "List.empty" || f == "Str.replace" || f == "Str.replaceMatch" || f == "Str.padLeft" || f == "Str.padRight" || f == "Map.set" || f == "Set.add" || f == "Map.getOrElse" || f == "Clock.iso8601" || f == "Clock.monotonic" || f == "Clock.realTime" || f == "Fs.mkdirs" || f == "Fs.canonicalize" || f == "Fs.exists" || f == "Fs.walk" || f == "Fs.delete" || f == "List.len" || f == "List.concat" || f == "List.reverse" || f == "List.head" || f == "List.tail" || f == "List.isEmpty" || f == "List.cons" || f == "List.at" || f == "Str.fromInt" || f == "Str.fromBool" || f == "Str.concat" || f == "Str.len" || f == "Str.byteLen" || f == "Str.byteSlice" || f == "Fs.read" || f == "Fs.write" || f == "Fs.list" || f == "IO.pure" || f == "Sys.getenv" || f == "Impurity.runKit" || f == "Net.serveOnce" || f == "Net.serve" || f == "Net.serveOnceTls" || f == "Net.serveTls" || isNetEmit(f) || f == "Queue.unbounded" || f == "Deferred.empty" || f == "Float.toInt" || f == "Float.fromInt" || f == "Map.isEmpty" || f == "Set.isEmpty" || f == "Map.nonEmpty" || f == "Set.nonEmpty" || f == "List.range" || f == "List.fill" || f == "List.getOrElse" || f == "List.padTo" || f == "List.max" || f == "List.min" || f == "List.sort" || f == "List.toSet" || f == "List.slice" || f == "List.isDefinedAt" || f == "List.lengthCompare" || f == "List.tabulate" || f == "List.segmentLength" || isTlKit(f) || f == "Net.retryAfterMillis" || f == "Oracle.sumTo" || f == "Fs.rename" || f == "Random.nextInt" || f == "Uuid.v4" def isNetEmit(f: String): Bool = f == "Net.httpGet" || f == "Net.httpHead" || f == "Net.httpDelete" || f == "Net.httpPost" || f == "Net.httpPut" || f == "Net.httpPatch" || f == "Net.tcpConnect" || f == "Net.tcpListen" || f == "Net.tcpAccept" || f == "Net.tcpRead" || f == "Net.tcpWrite" || f == "Net.tcpClose" || f == "Net.udpBind" || f == "Net.udpSend" || f == "Net.udpRecv" || f == "Net.udpClose" @@ -6390,7 +6390,7 @@ def decls(): String = join(declsA(), join(declsB(), join(declsC(), join(declsD(), join(declsE(), join(declsF(), join(declsG(), join(declsH(), join(declsI(), join(declsJ(), join(declsK(), join(declsL(), join(declsM(), join(declsN(), join(declsO(), join(declsP(), join(declsQ(), join(declsR(), join(declsS(), join(declsT(), join(declsU(), join(declsV(), join(declsW(), join(declsX(), nl())))))))))))))))))))))))) def declsR(): String = - join(decl("ptr @sz_string_lines(ptr)"), join(decl("ptr @sz_string_trim(ptr)"), join(decl("ptr @sz_string_to_lower(ptr)"), join(decl("ptr @sz_string_to_upper(ptr)"), join(decl("ptr @sz_string_capitalize(ptr)"), join(decl("ptr @sz_string_ureverse(ptr)"), join(decl("i64 @sz_string_is_empty(ptr)"), join(decl("i64 @sz_string_non_empty(ptr)"), join(decl("i64 @sz_string_is_blank(ptr)"), join(decl("ptr @sz_string_split(ptr, ptr)"), join(decl("ptr @sz_string_strip_prefix(ptr, ptr)"), join(decl("ptr @sz_string_strip_suffix(ptr, ptr)"), join(decl("i64 @sz_string_ends_with(ptr, ptr)"), join(decl("i64 @sz_string_uindex_of(ptr, ptr)"), join(decl("i64 @sz_string_ulast_index_of(ptr, ptr)"), join(decl("ptr @sz_string_replace(ptr, ptr, ptr)"), join(decl("ptr @sz_string_pad_left(ptr, i64, ptr)"), decl("ptr @sz_string_pad_right(ptr, i64, ptr)")))))))))))))))))) + join(decl("ptr @sz_string_lines(ptr)"), join(decl("ptr @sz_string_trim(ptr)"), join(decl("ptr @sz_string_to_lower(ptr)"), join(decl("ptr @sz_string_to_upper(ptr)"), join(decl("ptr @sz_string_capitalize(ptr)"), join(decl("ptr @sz_string_ureverse(ptr)"), join(decl("i64 @sz_string_is_empty(ptr)"), join(decl("i64 @sz_string_non_empty(ptr)"), join(decl("i64 @sz_string_is_blank(ptr)"), join(decl("ptr @sz_string_split(ptr, ptr)"), join(decl("ptr @sz_string_strip_prefix(ptr, ptr)"), join(decl("ptr @sz_string_strip_suffix(ptr, ptr)"), join(decl("i64 @sz_string_ends_with(ptr, ptr)"), join(decl("i64 @sz_string_uindex_of(ptr, ptr)"), join(decl("i64 @sz_string_ulast_index_of(ptr, ptr)"), join(decl("ptr @sz_string_replace(ptr, ptr, ptr)"), join(decl("ptr @sz_string_replace_match(ptr, ptr, ptr)"), join(decl("ptr @sz_string_capture(ptr, ptr)"), join(decl("ptr @sz_string_pad_left(ptr, i64, ptr)"), decl("ptr @sz_string_pad_right(ptr, i64, ptr)")))))))))))))))))))) def declsS(): String = join(decl("ptr @sz_map_union(ptr, ptr)"), join(decl("ptr @sz_map_intersect(ptr, ptr)"), join(decl("ptr @sz_map_diff(ptr, ptr)"), join(decl("ptr @sz_list_set_at(ptr, i64, ptr)"), join(decl("i64 @sz_list_starts_with(ptr, ptr)"), join(decl("i64 @sz_list_ends_with(ptr, ptr)"), join(decl("i64 @sz_list_same_elements(ptr, ptr)"), join(decl("ptr @sz_list_patch(ptr, i64, ptr, i64)"), join(decl("ptr @sz_json_stringify(ptr)"), join(decl("i64 @sz_json_is_bool(ptr)"), join(decl("i64 @sz_json_is_int(ptr)"), join(decl("i64 @sz_json_is_str(ptr)"), join(decl("i64 @sz_json_is_float(ptr)"), join(decl("i64 @sz_json_bool_or(ptr, i64)"), join(decl("ptr @sz_json_str_or(ptr, ptr)"), join(decl("double @sz_json_get_float(ptr, ptr, double)"), join(decl("ptr @sz_fs_exists(ptr)"), join(decl("ptr @sz_fs_delete(ptr)"), join(decl("ptr @sz_fs_rename(ptr, ptr)"), join(decl("ptr @sz_fs_walk(ptr)"), join(decl("ptr @sz_fs_join(ptr, ptr)"), join(decl("ptr @sz_fs_dirname(ptr)"), join(decl("ptr @sz_fs_basename(ptr)"), join(decl("i64 @sz_oracle_sum_to(i64)"), decl("ptr @sz_string_from_float(double)"))))))))))))))))))))))))) diff --git a/examples/compiler/src/Kits.scuzz b/examples/compiler/src/Kits.scuzz index 032ffa7d..228be48b 100644 --- a/examples/compiler/src/Kits.scuzz +++ b/examples/compiler/src/Kits.scuzz @@ -21,7 +21,7 @@ def allKits(): List[Kit] = List.concat(strKits(), List.concat(listKits(), List.concat(mapSetKits(), List.concat(ioKits(), List.concat(fsSysKits(), List.concat(jsonNetKits(), List.concat(uiKits(), extraKits()))))))) def strKits(): List[Kit] = - k("Str.fromInt", "Int" :: ns(), "String") :: k("Str.fromBool", "Bool" :: ns(), "String") :: k("Str.concat", "String" :: "String" :: ns(), "String") :: k("Str.len", "String" :: ns(), "Int") :: k("Str.byteLen", "String" :: ns(), "Int") :: k("Str.charAt", "String" :: "Int" :: ns(), "Int") :: k("Str.slice", "String" :: "Int" :: "Int" :: ns(), "String") :: k("Str.byteSlice", "String" :: "Int" :: "Int" :: ns(), "String") :: k("Str.toInt", "String" :: "Int" :: ns(), "Int") :: k("Str.repeat", "String" :: "Int" :: ns(), "String") :: k("Str.startsWith", "String" :: "String" :: ns(), "Bool") :: k("Str.endsWith", "String" :: "String" :: ns(), "Bool") :: k("Str.contains", "String" :: "String" :: ns(), "Bool") :: k("Str.matches", "String" :: "String" :: ns(), "Bool") :: k("Str.eq", "String" :: "String" :: ns(), "Bool") :: k("Str.indexOf", "String" :: "String" :: ns(), "Int") :: k("Str.lastIndexOf", "String" :: "String" :: ns(), "Int") :: k("Str.isEmpty", "String" :: ns(), "Bool") :: k("Str.nonEmpty", "String" :: ns(), "Bool") :: k("Str.isBlank", "String" :: ns(), "Bool") :: k("Str.lines", "String" :: ns(), "List[String]") :: k("Str.trim", "String" :: ns(), "String") :: k("Str.reverse", "String" :: ns(), "String") :: k("Str.toLower", "String" :: ns(), "String") :: k("Str.toUpper", "String" :: ns(), "String") :: k("Str.capitalize", "String" :: ns(), "String") :: k("Str.take", "String" :: "Int" :: ns(), "String") :: k("Str.drop", "String" :: "Int" :: ns(), "String") :: k("Str.takeRight", "String" :: "Int" :: ns(), "String") :: k("Str.dropRight", "String" :: "Int" :: ns(), "String") :: k("Str.padLeft", "String" :: "Int" :: "String" :: ns(), "String") :: k("Str.padRight", "String" :: "Int" :: "String" :: ns(), "String") :: k("Str.replace", "String" :: "String" :: "String" :: ns(), "String") :: k("Str.stripPrefix", "String" :: "String" :: ns(), "String") :: k("Str.stripSuffix", "String" :: "String" :: ns(), "String") :: k("Str.split", "String" :: "String" :: ns(), "List[String]") :: nsKit() + k("Str.fromInt", "Int" :: ns(), "String") :: k("Str.fromBool", "Bool" :: ns(), "String") :: k("Str.concat", "String" :: "String" :: ns(), "String") :: k("Str.len", "String" :: ns(), "Int") :: k("Str.byteLen", "String" :: ns(), "Int") :: k("Str.charAt", "String" :: "Int" :: ns(), "Int") :: k("Str.slice", "String" :: "Int" :: "Int" :: ns(), "String") :: k("Str.byteSlice", "String" :: "Int" :: "Int" :: ns(), "String") :: k("Str.toInt", "String" :: "Int" :: ns(), "Int") :: k("Str.repeat", "String" :: "Int" :: ns(), "String") :: k("Str.startsWith", "String" :: "String" :: ns(), "Bool") :: k("Str.endsWith", "String" :: "String" :: ns(), "Bool") :: k("Str.contains", "String" :: "String" :: ns(), "Bool") :: k("Str.matches", "String" :: "String" :: ns(), "Bool") :: k("Str.capture", "String" :: "String" :: ns(), "List[String]") :: k("Str.replaceMatch", "String" :: "String" :: "String" :: ns(), "String") :: k("Str.eq", "String" :: "String" :: ns(), "Bool") :: k("Str.indexOf", "String" :: "String" :: ns(), "Int") :: k("Str.lastIndexOf", "String" :: "String" :: ns(), "Int") :: k("Str.isEmpty", "String" :: ns(), "Bool") :: k("Str.nonEmpty", "String" :: ns(), "Bool") :: k("Str.isBlank", "String" :: ns(), "Bool") :: k("Str.lines", "String" :: ns(), "List[String]") :: k("Str.trim", "String" :: ns(), "String") :: k("Str.reverse", "String" :: ns(), "String") :: k("Str.toLower", "String" :: ns(), "String") :: k("Str.toUpper", "String" :: ns(), "String") :: k("Str.capitalize", "String" :: ns(), "String") :: k("Str.take", "String" :: "Int" :: ns(), "String") :: k("Str.drop", "String" :: "Int" :: ns(), "String") :: k("Str.takeRight", "String" :: "Int" :: ns(), "String") :: k("Str.dropRight", "String" :: "Int" :: ns(), "String") :: k("Str.padLeft", "String" :: "Int" :: "String" :: ns(), "String") :: k("Str.padRight", "String" :: "Int" :: "String" :: ns(), "String") :: k("Str.replace", "String" :: "String" :: "String" :: ns(), "String") :: k("Str.stripPrefix", "String" :: "String" :: ns(), "String") :: k("Str.stripSuffix", "String" :: "String" :: ns(), "String") :: k("Str.split", "String" :: "String" :: ns(), "List[String]") :: nsKit() def nsKit(): List[Kit] = [] diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index 7b14e9de..9f9efe79 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -376,13 +376,16 @@ def netPinTypes(): Bool = rejects("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))", "arg type mismatch") && rejects("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)", "needs IO[(Int, Map[String, String], String)]") && rejects("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)", "View.each needs A => View") && Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))") == "[]" && kitPinTypes() def kitPinTypes(): Bool = - rejects("def wrong[A](xs: List[Int], x: A): List[Int] = List.filter(xs, _ => x)", "arg type mismatch") && Check.check("def right[B](xs: List[Int]): List[String] = List.map(xs, n => \"x\")") == "[]" && Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") == "[]" + rejects("def wrong[A](xs: List[Int], x: A): List[Int] = List.filter(xs, _ => x)", "arg type mismatch") && Check.check("def right[B](xs: List[Int]): List[String] = List.map(xs, n => \"x\")") == "[]" && Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") == "[]" && strRegexTypes() + +def strRegexTypes(): Bool = + Check.check("def right(): List[String] = Str.capture(\"abc123\", \"([a-z]+)([0-9]+)\")") == "[]" && rejects("def wrong(): List[Int] = Str.capture(\"a\", \"(a)\")", "does not match declared") && Check.check("def right(): String = Str.replaceMatch(\"a1b\", \"[0-9]\", \"x\")") == "[]" && rejects("def wrong(): Int = Str.replaceMatch(\"a\", \"a\", \"b\")", "does not match declared") def listFilterTypes(): Bool = Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared") && rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare") && Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") == "[]" && rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared") && Check.check("def right(): List[String] = List.takeWhile([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && Check.check("def right(): List[String] = List.dropRight([\"a\", \"b\"], 1)") == "[]" def dumpUserTypeArgs(): String = - if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else if (!rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO")) Str.concat("flatA ", Check.check("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO")) Str.concat("handleA ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)")) else if (!rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function")) Str.concat("applyA ", Check.check("def wrong[A](x: A): Int = x.apply(1)")) else if (!rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO")) Str.concat("resA ", Check.check("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") != "[]") Str.concat("flatOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View")) Str.concat("eachA ", Check.check("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)")) else if (!rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool")) Str.concat("reqA ", Check.check("def wrong[A](n: Int, p: A): Int = n.require(p)")) else if (!rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch")) Str.concat("propA ", Check.check("def wrong[A](x: A): Int = Property.check(x, true, 1)")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))", "handleErrorWith type mismatch")) Str.concat("handlePure ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))")) else if (!rejects("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)", "handleErrorWith type mismatch")) Str.concat("handleLit ", Check.check("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)")) else if (!rejects("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))", "type mismatch: expected")) Str.concat("flatAnn ", Check.check("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)") != "[]") Str.concat("handleOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)")) else if (!rejects("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))", "arg type mismatch")) Str.concat("netPort ", Check.check("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))")) else if (!rejects("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)", "needs IO[(Int, Map[String, String], String)]")) Str.concat("netBody ", Check.check("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)", "View.each needs A => View")) Str.concat("eachFun ", Check.check("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)")) else if (Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))") != "[]") Str.concat("netOk ", Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))")) else if (!rejects("def wrong[A](xs: List[Int], x: A): List[Int] = List.filter(xs, _ => x)", "arg type mismatch")) Str.concat("kitFilter ", Check.check("def wrong[A](xs: List[Int], x: A): List[Int] = List.filter(xs, _ => x)")) else if (Check.check("def right[B](xs: List[Int]): List[String] = List.map(xs, n => \"x\")") != "[]") Str.concat("kitMapB ", Check.check("def right[B](xs: List[Int]): List[String] = List.map(xs, n => \"x\")")) else "userTypeArgs-other" + if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else if (!rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO")) Str.concat("flatA ", Check.check("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO")) Str.concat("handleA ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)")) else if (!rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function")) Str.concat("applyA ", Check.check("def wrong[A](x: A): Int = x.apply(1)")) else if (!rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO")) Str.concat("resA ", Check.check("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") != "[]") Str.concat("flatOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View")) Str.concat("eachA ", Check.check("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)")) else if (!rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool")) Str.concat("reqA ", Check.check("def wrong[A](n: Int, p: A): Int = n.require(p)")) else if (!rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch")) Str.concat("propA ", Check.check("def wrong[A](x: A): Int = Property.check(x, true, 1)")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))", "handleErrorWith type mismatch")) Str.concat("handlePure ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))")) else if (!rejects("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)", "handleErrorWith type mismatch")) Str.concat("handleLit ", Check.check("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)")) else if (!rejects("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))", "type mismatch: expected")) Str.concat("flatAnn ", Check.check("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)") != "[]") Str.concat("handleOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)")) else if (!rejects("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))", "arg type mismatch")) Str.concat("netPort ", Check.check("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))")) else if (!rejects("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)", "needs IO[(Int, Map[String, String], String)]")) Str.concat("netBody ", Check.check("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)", "View.each needs A => View")) Str.concat("eachFun ", Check.check("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)")) else if (Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))") != "[]") Str.concat("netOk ", Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))")) else if (!rejects("def wrong[A](xs: List[Int], x: A): List[Int] = List.filter(xs, _ => x)", "arg type mismatch")) Str.concat("kitFilter ", Check.check("def wrong[A](xs: List[Int], x: A): List[Int] = List.filter(xs, _ => x)")) else if (Check.check("def right[B](xs: List[Int]): List[String] = List.map(xs, n => \"x\")") != "[]") Str.concat("kitMapB ", Check.check("def right[B](xs: List[Int]): List[String] = List.map(xs, n => \"x\")")) else if (!rejects("def wrong(): List[Int] = Str.capture(\"a\", \"(a)\")", "does not match declared")) Str.concat("capMis ", Check.check("def wrong(): List[Int] = Str.capture(\"a\", \"(a)\")")) else if (Check.check("def right(): String = Str.replaceMatch(\"a1b\", \"[0-9]\", \"x\")") != "[]") Str.concat("repmOk ", Check.check("def right(): String = Str.replaceMatch(\"a1b\", \"[0-9]\", \"x\")")) else "userTypeArgs-other" def dumpListFilter(): String = if (!rejects("def wrong(): List[Int] = [1, \"x\"]", "list element")) "listElem" else if (!rejects("def wrong(): List[Int] = List.concat([1], [\"x\"])", "arg type mismatch")) "listConcat" else if (!rejects("def wrong(): List[String] = List.map([1], x => x + 1)", "does not match declared")) "listMap" else if (Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") != "[]") Str.concat("filterOk ", Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)")) else if (!rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared")) Str.concat("filterMis ", Check.check("def wrong(): List[Int] = List.filter([\"a\"], x => true)")) else if (!rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare")) Str.concat("filterPred ", Check.check("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)")) else if (Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") != "[]") Str.concat("takeOk ", Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)")) else if (!rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared")) Str.concat("takeMis ", Check.check("def wrong(): List[Int] = List.take([\"a\"], 1)")) else "listFilter-other" From af9a5cbf432569ef505cf03da5f8419365230f7b Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 17 Sep 2026 08:51:19 -0400 Subject: [PATCH 22/23] Format checker and emit oracles so kernel check can pass. --- examples/codegen/src/Main.scuzz | 6 +++--- examples/tyck/src/Main.scuzz | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/codegen/src/Main.scuzz b/examples/codegen/src/Main.scuzz index 7761a1cb..49de90a3 100644 --- a/examples/codegen/src/Main.scuzz +++ b/examples/codegen/src/Main.scuzz @@ -690,9 +690,6 @@ def dumpPtr(): String = def dumpOpt(): String = if (!irDiff(srcOpt(), wantOpt())) "srcOpt" else if (!irDiff(srcHead(), wantHead())) "srcHead" else if (!okHeadPair()) "okHeadPair" else if (!okNestCtor()) "okNestCtor" else if (!okNestTupField()) "okNestTupField" else if (!okNestCons()) "okNestCons" else "okOpt-other" -@main def main: IO[Unit] = - IO.println(if (allOk()) "ir-ok" else dumpAll()) - def reloadCaptureOrder(): Bool = Emit.captureSchema([Param("a", "Int", "", ""), Param("b", "String", "", "")], []) != Emit.captureSchema([Param("b", "String", "", ""), Param("a", "Int", "", "")], []) @@ -701,3 +698,6 @@ def reloadCaptureType(): Bool = def reloadRecordLayout(): Bool = Emit.captureSchema([Param("s", "Signal[State]", "", "")], [En(true, "State", [], [EnCase("State", [Param("value", "Int", "", "")])])]) != Emit.captureSchema([Param("s", "Signal[State]", "", "")], [En(true, "State", [], [EnCase("State", [Param("value", "String", "", "")])])]) + +@main def main: IO[Unit] = + IO.println(if (allOk()) "ir-ok" else dumpAll()) diff --git a/examples/tyck/src/Main.scuzz b/examples/tyck/src/Main.scuzz index 9f9efe79..f8b54843 100644 --- a/examples/tyck/src/Main.scuzz +++ b/examples/tyck/src/Main.scuzz @@ -361,7 +361,8 @@ def collectionTypes(): Bool = def userTypeArgs(): Bool = !Type.eqPinned("A", "String", "A" :: []) && rejects("def choose[A](x: A, y: A): A = x\ndef wrong(): Int = choose(1, \"x\")", "arg type mismatch") && Check.check("""def choose[A](x: A, y: A): A = x def right(): Int = choose(1, 2)""") == "[]" && rejects("""def identity[A](x: A): A = x -def wrong(): String = identity(1)""", "does not match declared") && rejects("def id[A](x: A): Int = x", "does not match declared") && rejects("def id[A](x: A): A = 1", "does not match declared") && Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") == "[]" && Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") == "[]" && rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got") && rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch") && rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs") && rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare") && rejects("def wrong[A](x: A): Int = -x", "expected Int or Float") && Check.check("def right(): Int = (n => n + 1)(1)") == "[]" && ioPinTypes() +def wrong(): String = identity(1)""", "does not match declared") && rejects("def id[A](x: A): Int = x", "does not match declared") && rejects("def id[A](x: A): A = 1", "does not match declared") && Check.check("""def id[A](x: A): A = x +def right(): Int = id(1)""") == "[]" && Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") == "[]" && rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got") && rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch") && rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs") && rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare") && rejects("def wrong[A](x: A): Int = -x", "expected Int or Float") && Check.check("def right(): Int = (n => n + 1)(1)") == "[]" && ioPinTypes() def ioPinTypes(): Bool = rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO") && rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO") && rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function") && rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO") && Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") == "[]" && viewPinTypes() @@ -385,7 +386,9 @@ def listFilterTypes(): Bool = Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared") && rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare") && Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") == "[]" && rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared") && Check.check("def right(): List[String] = List.takeWhile([\"a\", \"\"], x => Str.len(x) > 0)") == "[]" && Check.check("def right(): List[String] = List.dropRight([\"a\", \"b\"], 1)") == "[]" def dumpUserTypeArgs(): String = - if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)") != "[]") Str.concat("idOk ", Check.check("def id[A](x: A): A = x\ndef right(): Int = id(1)")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else if (!rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO")) Str.concat("flatA ", Check.check("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO")) Str.concat("handleA ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)")) else if (!rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function")) Str.concat("applyA ", Check.check("def wrong[A](x: A): Int = x.apply(1)")) else if (!rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO")) Str.concat("resA ", Check.check("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") != "[]") Str.concat("flatOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View")) Str.concat("eachA ", Check.check("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)")) else if (!rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool")) Str.concat("reqA ", Check.check("def wrong[A](n: Int, p: A): Int = n.require(p)")) else if (!rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch")) Str.concat("propA ", Check.check("def wrong[A](x: A): Int = Property.check(x, true, 1)")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))", "handleErrorWith type mismatch")) Str.concat("handlePure ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))")) else if (!rejects("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)", "handleErrorWith type mismatch")) Str.concat("handleLit ", Check.check("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)")) else if (!rejects("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))", "type mismatch: expected")) Str.concat("flatAnn ", Check.check("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)") != "[]") Str.concat("handleOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)")) else if (!rejects("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))", "arg type mismatch")) Str.concat("netPort ", Check.check("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))")) else if (!rejects("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)", "needs IO[(Int, Map[String, String], String)]")) Str.concat("netBody ", Check.check("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)", "View.each needs A => View")) Str.concat("eachFun ", Check.check("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)")) else if (Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))") != "[]") Str.concat("netOk ", Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))")) else if (!rejects("def wrong[A](xs: List[Int], x: A): List[Int] = List.filter(xs, _ => x)", "arg type mismatch")) Str.concat("kitFilter ", Check.check("def wrong[A](xs: List[Int], x: A): List[Int] = List.filter(xs, _ => x)")) else if (Check.check("def right[B](xs: List[Int]): List[String] = List.map(xs, n => \"x\")") != "[]") Str.concat("kitMapB ", Check.check("def right[B](xs: List[Int]): List[String] = List.map(xs, n => \"x\")")) else if (!rejects("def wrong(): List[Int] = Str.capture(\"a\", \"(a)\")", "does not match declared")) Str.concat("capMis ", Check.check("def wrong(): List[Int] = Str.capture(\"a\", \"(a)\")")) else if (Check.check("def right(): String = Str.replaceMatch(\"a1b\", \"[0-9]\", \"x\")") != "[]") Str.concat("repmOk ", Check.check("def right(): String = Str.replaceMatch(\"a1b\", \"[0-9]\", \"x\")")) else "userTypeArgs-other" + if (Type.eqPinned("A", "String", "A" :: [])) "eqPinnedAString" else if (!rejects("def id[A](x: A): Int = x", "does not match declared")) Str.concat("idInt ", Check.check("def id[A](x: A): Int = x")) else if (!rejects("def id[A](x: A): A = 1", "does not match declared")) Str.concat("idLit ", Check.check("def id[A](x: A): A = 1")) else if (Check.check("""def id[A](x: A): A = x +def right(): Int = id(1)""") != "[]") Str.concat("idOk ", Check.check("""def id[A](x: A): A = x +def right(): Int = id(1)""")) else if (Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)") != "[]") Str.concat("wrapPure ", Check.check("def wrap[A](x: A): IO[A] = IO.pure(x)")) else if (!rejects("def wrong[A](x: A): IO[Unit] = IO.println(x)", "expected String, got")) Str.concat("printA ", Check.check("def wrong[A](x: A): IO[Unit] = IO.println(x)")) else if (!rejects("def wrong[A](x: A): Int = Str.len(x)", "arg type mismatch")) Str.concat("lenA ", Check.check("def wrong[A](x: A): Int = Str.len(x)")) else if (!rejects("def wrong[A](x: A): Int = x + 1", "arithmetic needs")) Str.concat("addA ", Check.check("def wrong[A](x: A): Int = x + 1")) else if (!rejects("def wrong[A](x: A): Bool = x > 0", "ordered compare")) Str.concat("ordA ", Check.check("def wrong[A](x: A): Bool = x > 0")) else if (!rejects("def wrong[A](x: A): Int = -x", "expected Int or Float")) Str.concat("negA ", Check.check("def wrong[A](x: A): Int = -x")) else if (Check.check("def right(): Int = (n => n + 1)(1)") != "[]") Str.concat("lamAdd ", Check.check("def right(): Int = (n => n + 1)(1)")) else if (!rejects("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))", "flatMap needs IO")) Str.concat("flatA ", Check.check("def wrong[A](x: A): IO[A] = x.flatMap(y => IO.pure(y))")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)", "handleErrorWith needs IO")) Str.concat("handleA ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => x)")) else if (!rejects("def wrong[A](x: A): Int = x.apply(1)", "apply needs a function")) Str.concat("applyA ", Check.check("def wrong[A](x: A): Int = x.apply(1)")) else if (!rejects("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))", "Resource.make needs IO")) Str.concat("resA ", Check.check("def wrong[A](x: A): Resource[A] = Resource.make(x, _ => IO.pure(()))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))") != "[]") Str.concat("flatOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.flatMap(x => IO.pure(x))")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)", "View.each needs View")) Str.concat("eachA ", Check.check("def wrong[A](xs: Signal[List[Int]], x: A): View = View.each(xs, _ => x)")) else if (!rejects("def wrong[A](n: Int, p: A): Int = n.require(p)", "require pred must be Bool")) Str.concat("reqA ", Check.check("def wrong[A](n: Int, p: A): Int = n.require(p)")) else if (!rejects("def wrong[A](x: A): Int = Property.check(x, true, 1)", "Property.check arg type mismatch")) Str.concat("propA ", Check.check("def wrong[A](x: A): Int = Property.check(x, true, 1)")) else if (!rejects("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))", "handleErrorWith type mismatch")) Str.concat("handlePure ", Check.check("def wrong[A](io: IO[Int], x: A): IO[Int] = io.handleErrorWith(_ => IO.pure(x))")) else if (!rejects("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)", "handleErrorWith type mismatch")) Str.concat("handleLit ", Check.check("def wrong[A](io: IO[A], other: IO[Int]): IO[A] = io.handleErrorWith(_ => other)")) else if (!rejects("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))", "type mismatch: expected")) Str.concat("flatAnn ", Check.check("def wrong[A](io: IO[Int]): IO[Int] = io.flatMap((x: A) => IO.pure(1))")) else if (Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)") != "[]") Str.concat("handleOk ", Check.check("def right[A](io: IO[A]): IO[A] = io.handleErrorWith(_ => io)")) else if (!rejects("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))", "arg type mismatch")) Str.concat("netPort ", Check.check("def wrong[A](p: A): IO[Unit] = Net.serve(p, _ => IO.pure((200, Map.empty(), \"\")))")) else if (!rejects("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)", "needs IO[(Int, Map[String, String], String)]")) Str.concat("netBody ", Check.check("def wrong[A](io: IO[A]): IO[Unit] = Net.serve(8080, _ => io)")) else if (!rejects("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)", "View.each needs A => View")) Str.concat("eachFun ", Check.check("def wrong[A](xs: Signal[List[Int]], f: A => View): View = View.each(xs, f)")) else if (Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))") != "[]") Str.concat("netOk ", Check.check("def right(): IO[Unit] = Net.serve(8080, _ => IO.pure((200, Map.empty(), \"\")))")) else if (!rejects("def wrong[A](xs: List[Int], x: A): List[Int] = List.filter(xs, _ => x)", "arg type mismatch")) Str.concat("kitFilter ", Check.check("def wrong[A](xs: List[Int], x: A): List[Int] = List.filter(xs, _ => x)")) else if (Check.check("def right[B](xs: List[Int]): List[String] = List.map(xs, n => \"x\")") != "[]") Str.concat("kitMapB ", Check.check("def right[B](xs: List[Int]): List[String] = List.map(xs, n => \"x\")")) else if (!rejects("def wrong(): List[Int] = Str.capture(\"a\", \"(a)\")", "does not match declared")) Str.concat("capMis ", Check.check("def wrong(): List[Int] = Str.capture(\"a\", \"(a)\")")) else if (Check.check("def right(): String = Str.replaceMatch(\"a1b\", \"[0-9]\", \"x\")") != "[]") Str.concat("repmOk ", Check.check("def right(): String = Str.replaceMatch(\"a1b\", \"[0-9]\", \"x\")")) else "userTypeArgs-other" def dumpListFilter(): String = if (!rejects("def wrong(): List[Int] = [1, \"x\"]", "list element")) "listElem" else if (!rejects("def wrong(): List[Int] = List.concat([1], [\"x\"])", "arg type mismatch")) "listConcat" else if (!rejects("def wrong(): List[String] = List.map([1], x => x + 1)", "does not match declared")) "listMap" else if (Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)") != "[]") Str.concat("filterOk ", Check.check("def right(): List[String] = List.filter([\"a\", \"\"], x => Str.len(x) > 0)")) else if (!rejects("def wrong(): List[Int] = List.filter([\"a\"], x => true)", "does not match declared")) Str.concat("filterMis ", Check.check("def wrong(): List[Int] = List.filter([\"a\"], x => true)")) else if (!rejects("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)", "ordered compare")) Str.concat("filterPred ", Check.check("def wrong(): List[String] = List.filter([\"a\"], n => n > 0)")) else if (Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)") != "[]") Str.concat("takeOk ", Check.check("def right(): List[Int] = List.take([1, 2, 3], 2)")) else if (!rejects("def wrong(): List[Int] = List.take([\"a\"], 1)", "does not match declared")) Str.concat("takeMis ", Check.check("def wrong(): List[Int] = List.take([\"a\"], 1)")) else "listFilter-other" From 9591288da5f47f8c9e1836ec6ff241606671ab17 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 17 Sep 2026 09:49:40 -0400 Subject: [PATCH 23/23] Wait for the iOS app on quit and stop capping URLSession at 1s. --- crates/embedder-mobile/shells/ios/run_sim.sh | 12 +++++++++++- crates/embedder-mobile/shells/ios/test_loop.py | 8 ++++---- crates/runtime/src/net_apple.m | 5 +++-- crates/runtime/tests/test_net_apple.c | 12 ++++++++++-- crates/runtime/tests/test_net_apple.py | 2 +- examples/cli/src/Ios.scuzz | 2 +- 6 files changed, 30 insertions(+), 11 deletions(-) diff --git a/crates/embedder-mobile/shells/ios/run_sim.sh b/crates/embedder-mobile/shells/ios/run_sim.sh index 62f97e11..0e359b50 100644 --- a/crates/embedder-mobile/shells/ios/run_sim.sh +++ b/crates/embedder-mobile/shells/ios/run_sim.sh @@ -8,6 +8,11 @@ PACKAGE="$OUT/package/ios" MODE="${3:-run}" CONSOLE="" BUNDLE="" +APP_NAME="" + +app_alive() { + [ -n "$APP_NAME" ] && pgrep -f "/${APP_NAME}.app/${APP_NAME}" >/dev/null +} stop_app() { if [ -n "$BUNDLE" ]; then @@ -18,7 +23,11 @@ stop_app() { wait "$CONSOLE" 2>/dev/null || true CONSOLE="" fi - + n=0 + while app_alive && [ "$n" -lt 100 ]; do + n=$((n + 1)) + sleep 0.1 + done } trap stop_app EXIT @@ -28,6 +37,7 @@ trap 'exit 143' TERM HUP launch() { local name next_bundle name="$(sed -n 's/^name = "\(.*\)"/\1/p' "$PACKAGE/package.toml")" + APP_NAME="$name" next_bundle="$(/usr/libexec/PlistBuddy -c Print:CFBundleIdentifier "$PACKAGE/$name.app/Info.plist")" echo "Install $name on the simulator." if ! xcrun simctl install "$DEVICE" "$PACKAGE/$name.app"; then diff --git a/crates/embedder-mobile/shells/ios/test_loop.py b/crates/embedder-mobile/shells/ios/test_loop.py index 3c946e62..97e0aa10 100644 --- a/crates/embedder-mobile/shells/ios/test_loop.py +++ b/crates/embedder-mobile/shells/ios/test_loop.py @@ -142,8 +142,8 @@ def shows(text): working = app_pid() proc.stdin.write("q\n") proc.stdin.flush() - assert proc.wait(timeout=10) == 0, "quit fails" - wait_for(lambda: dead(working), "quit cleanup", 10) + assert proc.wait(timeout=30) == 0, "quit fails" + wait_for(lambda: dead(working), "quit cleanup", 30) thread.join(timeout=10) assert not thread.is_alive(), "console process survives quit" dump = json.loads((app / "output path" / "debug.json").read_text()) @@ -152,8 +152,8 @@ def shows(text): wait_for(lambda: "ios-loop-v3" in lines and launches(lines), "second launch") working = app_pid() proc.send_signal(signal.SIGINT) - assert proc.wait(timeout=10) in (-signal.SIGINT, 130), "interrupt status is wrong" - wait_for(lambda: dead(working), "interrupt cleanup", 10) + assert proc.wait(timeout=30) in (-signal.SIGINT, 130), "interrupt status is wrong" + wait_for(lambda: dead(working), "interrupt cleanup", 30) thread.join(timeout=10) assert not thread.is_alive(), "console process survives interruption" subprocess.run([sys.executable, str(Path(__file__).with_name("test_viewport.py")), device], check=True) diff --git a/crates/runtime/src/net_apple.m b/crates/runtime/src/net_apple.m index 0688cf79..e3750e54 100644 --- a/crates/runtime/src/net_apple.m +++ b/crates/runtime/src/net_apple.m @@ -60,8 +60,9 @@ - (void)start { config.URLCredentialStorage = nil; config.HTTPShouldSetCookies = NO; config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; - config.timeoutIntervalForRequest = 1; - config.timeoutIntervalForResource = 5; + /* Idle bounds only. IO.timeout cancels the task through the finalizer. */ + config.timeoutIntervalForRequest = 30; + config.timeoutIntervalForResource = 60; config.waitsForConnectivity = NO; NSOperationQueue *queue = [NSOperationQueue new]; queue.maxConcurrentOperationCount = 1; diff --git a/crates/runtime/tests/test_net_apple.c b/crates/runtime/tests/test_net_apple.c index 01274593..4ce627b8 100644 --- a/crates/runtime/tests/test_net_apple.c +++ b/crates/runtime/tests/test_net_apple.c @@ -31,6 +31,12 @@ static void drop(SzIoResult result) { sz_release(result.value); sz_release(resul static int status(SzIoResult result, int expected) { return result.ok && sz_unbox_i64(((SzPair *)result.value)->left) == expected; } +static void check_http(SzIoResult result, int expected, const char *name) { + if (status(result, expected)) return; + check(0, name); + if (!result.ok && result.error && result.error->message) + fprintf(stderr, "%s\n", sz_string_cstr(result.error->message)); +} static const char *body(SzIoResult result) { return sz_string_cstr(((SzPair *)((SzPair *)result.value)->right)->right); } @@ -46,7 +52,7 @@ int scuzz_net_apple_proof(void) { SzMap *headers = header(NULL, "X-Proof", "custom"); for (size_t i = 0; i < sizeof methods / sizeof *methods; i++) { SzIoResult r = run(request(base, "/echo", methods[i], headers)); - check(status(r, 200), methods[i]); + check_http(r, 200, methods[i]); if (r.ok) { check(!strcmp(body(r), !strcmp(methods[i], "HEAD") ? "" : methods[i]), "method response"); SzString *key = sz_string_from_cstr("x-method"); @@ -80,7 +86,9 @@ int scuzz_net_apple_proof(void) { check(descriptors() <= before + 4, "cancellation closes request descriptors"); r = run(request(tls, "/echo", "GET", NULL)); int trusted = getenv("SCUZZ_NET_TRUSTED") != NULL; - check(trusted ? status(r, 200) : !r.ok, "platform certificate trust on loopback"); drop(r); + if (trusted) check_http(r, 200, "platform certificate trust on loopback"); + else check(!r.ok, "platform certificate trust on loopback"); + drop(r); if (!trusted) { r = run(request("https://example.com", "/", "GET", NULL)); check(status(r, 200), "platform public certificate trust without OpenSSL paths"); diff --git a/crates/runtime/tests/test_net_apple.py b/crates/runtime/tests/test_net_apple.py index bddfa905..139dfd1b 100644 --- a/crates/runtime/tests/test_net_apple.py +++ b/crates/runtime/tests/test_net_apple.py @@ -114,7 +114,7 @@ def prove_ios(cli, project, temp, env): subprocess.run(["codesign", "--force", "--sign", "-", "--timestamp=none", str(app)], check=True) subprocess.run(["xcrun", "simctl", "install", device, str(app)], check=True) child_env = dict(os.environ, **{"SIMCTL_CHILD_" + key: value for key, value in env.items() if key.startswith(("SCUZZ_NET_", "SSL_CERT_"))}, SIMCTL_CHILD_SCUZZ_NET_TRUSTED="1") - result = subprocess.run(["xcrun", "simctl", "launch", "--console", device, "dev.scuzz.netproof"], env=child_env, capture_output=True, text=True, timeout=45) + result = subprocess.run(["xcrun", "simctl", "launch", "--console", device, "dev.scuzz.netproof"], env=child_env, capture_output=True, text=True, timeout=90) print(result.stdout + result.stderr, end="", flush=True) assert result.returncode == 0 and "Apple Net proof ok" in result.stdout, "iOS Net contract fails" subprocess.run(["xcrun", "simctl", "install", device, str(ios / "network-ui.app")], check=True) diff --git a/examples/cli/src/Ios.scuzz b/examples/cli/src/Ios.scuzz index 3d432439..ccc87379 100644 --- a/examples/cli/src/Ios.scuzz +++ b/examples/cli/src/Ios.scuzz @@ -96,7 +96,7 @@ def startConsole(dir: String, out: String, sim: Sim, watch: Bool, before: String def stop(pid: Int): IO[Unit] = Sys.childWrite(pid, """quit -""").handleErrorWith(_ => IO.pure(())).flatMap(_ => Sys.childClose(pid).handleErrorWith(_ => IO.pure(())).flatMap(_ => IO.timeout(5000, stopped(pid)).handleErrorWith(_ => Sys.kill(pid)))) +""").handleErrorWith(_ => IO.pure(())).flatMap(_ => Sys.childClose(pid).handleErrorWith(_ => IO.pure(())).flatMap(_ => IO.timeout(20000, stopped(pid)).handleErrorWith(_ => Sys.kill(pid)))) def stopped(pid: Int): IO[Unit] = Sys.alive(pid).flatMap(alive => if (alive == 0) IO.pure(()) else IO.sleep(50).flatMap(_ => stopped(pid)))