Skip to content

Commit 7a5cc25

Browse files
chengluyuLPTK
andauthored
Prohibit access to non-val class parameters through pattern matching (#300)
Co-authored-by: Lionel Parreaux <lionel.parreaux@gmail.com>
1 parent 618b206 commit 7a5cc25

File tree

13 files changed

+217
-72
lines changed

13 files changed

+217
-72
lines changed

hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx):
479479
case (param, arg) :: args =>
480480
val (cse, blk) = mkArgs(args)
481481
(cse, Assign(arg, Select(sr, param.id/*FIXME incorrect Ident?*/)(S(param)), blk))
482-
mkMatch(mkArgs(clsParams.zip(args)))
482+
mkMatch(mkArgs(clsParams.iterator.zip(args).collect { case (s1, S(s2)) => (s1, s2) }.toList))
483483
case Pattern.Tuple(len, inf) => mkMatch(Case.Tup(len, inf) -> go(tail, topLevel = false))
484484
case Split.Else(els) =>
485485
if k.isInstanceOf[TailOp] && isIf then term_nonTail(els)(k)

hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,22 @@ import ucs.DeBrujinSplit
77

88
/** Flat patterns for pattern matching */
99
enum Pattern extends AutoLocated:
10+
1011
case Lit(literal: Literal)
11-
case ClassLike(sym: ClassSymbol | ModuleSymbol, trm: Term, parameters: Opt[List[BlockLocalSymbol]], var refined: Bool)(val tree: Tree)
12+
13+
/** An individual argument is None when it is not matched, i.e. when an underscore is used there.
14+
* The whole argument list is None when no argument list is being matched at all, as in `x is Some then ...`. */
15+
case ClassLike(
16+
sym: ClassSymbol | ModuleSymbol,
17+
trm: Term,
18+
args: Opt[List[Opt[BlockLocalSymbol]]],
19+
var refined: Bool,
20+
)(val tree: Tree)
21+
1222
case Synonym(symbol: PatternSymbol, patternArguments: Ls[(split: DeBrujinSplit, tree: Tree)])
23+
1324
case Tuple(size: Int, inf: Bool)
25+
1426
case Record(entries: List[(Ident -> BlockLocalSymbol)])
1527

1628
def subTerms: Ls[Term] = this match
@@ -22,15 +34,16 @@ enum Pattern extends AutoLocated:
2234

2335
def children: Ls[Located] = this match
2436
case Lit(literal) => literal :: Nil
25-
case ClassLike(_, t, parameters, _) => t :: parameters.toList.flatten
37+
case ClassLike(_, t, args, _) =>
38+
t :: args.fold(Nil)(_.collect { case S(symbol) => symbol })
2639
case Synonym(_, arguments) => arguments.map(_.tree)
2740
case Tuple(fields, _) => Nil
2841
case Record(entries) => entries.flatMap { case (nme, als) => nme :: als :: Nil }
2942

3043
def showDbg: Str = this match
3144
case Lit(literal) => literal.idStr
3245
case ClassLike(sym, t, ps, rfd) => (if rfd then "refined " else "") +
33-
sym.nme + ps.fold("")(_.mkString("(", ", ", ")"))
46+
sym.nme + ps.fold("")(_.iterator.map(_.fold("_")(_.toString)).mkString("(", ", ", ")"))
3447
case Synonym(symbol, arguments) =>
3548
symbol.nme + arguments.iterator.map(_.tree.showDbg).mkString("(", ", ", ")")
3649
case Tuple(size, inf) => "[]" + (if inf then ">=" else "=") + size

hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ extension (split: DeBrujinSplit)
347347
go(body, ctx2)
348348
val select = scoped("ucs:sel"):
349349
elab.reference(symbol).getOrElse(Term.Error)
350-
val pattern = Pattern.ClassLike(symbol, select, S(subSymbols), false)(Empty())
350+
val pattern = Pattern.ClassLike(symbol, select, S(subSymbols.map(S.apply)), false)(Empty())
351351
semantics.Branch(ctx(scrutinee - 1)(), pattern, consequent2) ~: go(alternative, ctx)
352352
case ClassLike(ConstructorLike.Symbol(symbol: ModuleSymbol)) =>
353353
val select = scoped("ucs:sel"):

hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Keyword.{as, and, `do`, `else`, is, let, `then`}
1111
import collection.mutable.{HashMap, SortedSet}
1212
import Elaborator.{ctx, Ctxl}
1313
import scala.annotation.targetName
14+
import hkmc2.semantics.ClassDef.Parameterized
1415

1516
object Desugarer:
1617
extension (op: Keyword.Infix)
@@ -478,19 +479,27 @@ class Desugarer(val elaborator: Elaborator)
478479
val clsTrm = elaborator.cls(ctor, inAppPrefix = false)
479480
clsTrm.symbol.flatMap(_.asClsLike) match
480481
case S(cls: ClassSymbol) =>
481-
val arity = cls.arity
482-
if arity =/= args.length then
483-
val m = args.length.toString
484-
error:
485-
if arity == 0 then
486-
msg"the constructor does not take any arguments but found $m" -> app.toLoc
487-
else
488-
msg"mismatched arity: expect ${arity.toString}, found $m" -> app.toLoc
489-
val params = scrutSymbol.getSubScrutinees(cls)
482+
val paramSymbols = cls.defn match
483+
case S(Parameterized(params = paramList)) =>
484+
if paramList.params.size =/= args.length then
485+
val n = args.length.toString
486+
val m = paramList.params.size.toString
487+
error:
488+
if paramList.params.isEmpty then
489+
msg"the constructor does not take any arguments but found $n" -> app.toLoc
490+
else
491+
msg"mismatched arity: expect $m, found $n" -> app.toLoc
492+
scrutSymbol.getSubScrutinees(cls).iterator.zip(paramList.params).map:
493+
case (symbol, Param(flags = FldFlags(value = true))) => R(symbol)
494+
case (_, Param(_, paramSymbol, _)) => L(paramSymbol) // to report errors
495+
.toList
496+
case S(_) | N =>
497+
error(msg"class ${cls.name} does not have parameters" -> ctor.toLoc)
498+
Nil
490499
Branch(
491500
ref,
492-
Pattern.ClassLike(cls, clsTrm, S(params), false)(ctor), // TODO: refined?
493-
subMatches(params zip args, sequel)(Split.End)(ctx)
501+
Pattern.ClassLike(cls, clsTrm, S(paramSymbols.map(_.toOption)), false)(ctor), // TODO: refined?
502+
subMatches(paramSymbols.zip(args), sequel)(Split.End)(ctx)
494503
) ~: fallback
495504
case S(pat: PatternSymbol) if compile =>
496505
// When we support extraction parameters, they need to be handled here.
@@ -550,26 +559,26 @@ class Desugarer(val elaborator: Elaborator)
550559
val (wrapRest, restMatches) = rest match
551560
case S((rest, last)) =>
552561
val (wrapLast, reversedLastMatches) = last.reverseIterator.zipWithIndex
553-
.foldLeft[(Split => Split, Ls[(BlockLocalSymbol, Tree)])]((identity, Nil)):
562+
.foldLeft[(Split => Split, Ls[(Right[Nothing, BlockLocalSymbol], Tree)])]((identity, Nil)):
554563
case ((wrapInner, matches), (pat, lastIndex)) =>
555564
val sym = scrutSymbol.getTupleLastSubScrutinee(lastIndex)
556565
val wrap = (split: Split) =>
557566
Split.Let(sym, callTupleGet(ref, -1 - lastIndex, sym), wrapInner(split))
558-
(wrap, (sym, pat) :: matches)
567+
(wrap, (R(sym), pat) :: matches)
559568
val lastMatches = reversedLastMatches.reverse
560569
rest match
561570
case N => (wrapLast, lastMatches)
562571
case S(pat) =>
563572
val sym = TempSymbol(N, "rest")
564573
val wrap = (split: Split) =>
565574
Split.Let(sym, app(tupleSlice, tup(fld(ref), fld(int(lead.length)), fld(int(last.length))), sym), wrapLast(split))
566-
(wrap, (sym, pat) :: lastMatches)
575+
(wrap, (R(sym), pat) :: lastMatches)
567576
case N => (identity: Split => Split, Nil)
568577
val (wrap, matches) = lead.zipWithIndex.foldRight((wrapRest, restMatches)):
569578
case ((pat, i), (wrapInner, matches)) =>
570579
val sym = scrutSymbol.getTupleLeadSubScrutinee(i)
571580
val wrap = (split: Split) => Split.Let(sym, Term.SynthSel(ref, Ident(s"$i"))(N), wrapInner(split))
572-
(wrap, (sym, pat) :: matches)
581+
(wrap, (R(sym), pat) :: matches)
573582
Branch(
574583
ref,
575584
Pattern.Tuple(lead.length + rest.fold(0)(_._2.length), rest.isDefined),
@@ -609,16 +618,29 @@ class Desugarer(val elaborator: Elaborator)
609618
/** Desugar a list of sub-patterns (with their corresponding scrutinees).
610619
* This is called when handling nested patterns. The caller is responsible
611620
* for providing the symbols of scrutinees.
621+
*
622+
* @param matches a list of pairs consisting of a scrutinee and a pattern.
623+
* Each scrutinee is represented by `Either[VarSymbol, BlockLocalSymbol]`.
624+
* If it is not accessible due to the corresponding parameter not being
625+
* declared with `val`, it will be the `Left` of the parameter symbol for
626+
* error reporting.
627+
* @param sequel the innermost split
612628
*/
613-
def subMatches(matches: Ls[(BlockLocalSymbol, Tree)],
629+
def subMatches(matches: Ls[(Either[VarSymbol, BlockLocalSymbol], Tree)],
614630
sequel: Sequel): Split => Sequel = matches match
615631
case Nil => _ => ctx => trace(
616632
pre = s"subMatches (done) <<< Nil",
617633
post = (r: Split) => s"subMatches >>> ${r.showDbg}"
618634
):
619635
sequel(ctx)
620636
case (_, Under()) :: rest => subMatches(rest, sequel)
621-
case (scrutinee, pattern) :: rest => fallback => trace(
637+
case (L(paramSymbol), pattern) :: rest =>
638+
error(msg"This pattern cannot be matched" -> pattern.toLoc,
639+
msg"because the corresponding parameter `${paramSymbol.name}` is not publicly accessible" -> paramSymbol.toLoc,
640+
msg"Suggestion: use a wildcard pattern `_` in this position" -> N,
641+
msg"Suggestion: mark this parameter with `val` so it becomes accessible" -> N)
642+
subMatches(rest, sequel)
643+
case (R(scrutinee), pattern) :: rest => fallback => trace(
622644
pre = s"subMatches (nested) <<< $scrutinee is $pattern",
623645
post = (r: Sequel) => s"subMatches (nested) >>>"
624646
):

hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ trait DesugaringBase(using state: State):
3030
protected def matchResultPattern(parameters: Opt[List[BlockLocalSymbol]]): Ctxl[Pattern.ClassLike] =
3131
val (classRef, classSym) = matchResultClass
3232
val classSel = Term.SynthSel(classRef, Ident("class"))(S(classSym))
33-
Pattern.ClassLike(classSym, classSel, parameters, false)(Empty())
33+
Pattern.ClassLike(classSym, classSel, parameters.map(_.map(S.apply)), false)(Empty())
3434

3535
/** Make a term that looks like `runtime.MatchFailure` with its symbol. */
3636
protected lazy val matchFailureClass: Ctxl[(Term.Sel | Term.SynthSel, ClassSymbol)] =
@@ -40,7 +40,7 @@ trait DesugaringBase(using state: State):
4040
protected def matchFailurePattern(parameters: Opt[List[BlockLocalSymbol]]): Ctxl[Pattern.ClassLike] =
4141
val (classRef, classSym) = matchResultClass
4242
val classSel = Term.SynthSel(classRef, Ident("class"))(S(classSym))
43-
Pattern.ClassLike(classSym, classSel, parameters, false)(Empty())
43+
Pattern.ClassLike(classSym, classSel, parameters.map(_.map(S.apply)), false)(Empty())
4444

4545
protected lazy val tupleSlice = sel(sel(state.runtimeSymbol.ref(), "Tuple"), "slice")
4646
protected lazy val tupleGet = sel(sel(state.runtimeSymbol.ref(), "Tuple"), "get")

hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class Normalization(elaborator: Elaborator)(using raise: Raise, ctx: Ctx):
4646
def =:=(rhs: Pattern): Bool = (lhs, rhs) match
4747
case (c1: Pattern.ClassLike, c2: Pattern.ClassLike) => c1.sym === c2.sym
4848
case (Pattern.Lit(l1), Pattern.Lit(l2)) => l1 === l2
49-
case (Pattern.Tuple(n1, b1), Pattern.Tuple(n2, b2)) => n1 == n2 && b1 == b2
49+
case (Pattern.Tuple(n1, b1), Pattern.Tuple(n2, b2)) => n1 === n2 && b1 === b2
5050
case (_, _) => false
5151
/** Checks if `lhs` can be subsumed under `rhs`. */
5252
def <:<(rhs: Pattern): Bool = compareCasePattern(lhs, rhs)
@@ -223,8 +223,9 @@ class Normalization(elaborator: Elaborator)(using raise: Raise, ctx: Ctx):
223223
private def aliasBindings(p: Pattern, q: Pattern): Split => Split = (p, q) match
224224
case (Pattern.ClassLike(_, _, S(ps1), _), Pattern.ClassLike(_, _, S(ps2), _)) =>
225225
ps1.iterator.zip(ps2.iterator).foldLeft(identity[Split]):
226-
case (acc, (p1, p2)) if p1 == p2 => acc
227-
case (acc, (p1, p2)) => innermost => Split.Let(p2, p1.ref(), acc(innermost))
226+
case (acc, (S(p1), S(p2))) if p1 === p2 => acc
227+
case (acc, (S(p1), S(p2))) => innermost => Split.Let(p2, p1.ref(), acc(innermost))
228+
case (acc, (_, _)) => acc
228229
case (_, _) => identity
229230
end Normalization
230231

@@ -240,7 +241,7 @@ object Normalization:
240241
case (ClassLike(cs: ClassSymbol, _, _, _), ClassLike(blt.`Object`, _, _, _))
241242
if !ctx.builtins.virtualClasses.contains(cs) => true
242243
case (ClassLike(cs: ModuleSymbol, _, _, _), ClassLike(blt.`Object`, _, _, _)) => true
243-
case (Tuple(n1, false), Tuple(n2, false)) if n1 == n2 => true
244+
case (Tuple(n1, false), Tuple(n2, false)) if n1 === n2 => true
244245
case (Tuple(n1, _), Tuple(n2, true)) if n2 <= n1 => true
245246
case (ClassLike(`Int`, _, _, _), ClassLike(blt.`Num`, _, _, _)) => true
246247
// case (s1: ClassSymbol, s2: ClassSymbol) => s1 <:< s2 // TODO: find a way to check inheritance

hkmc2/shared/src/test/mlscript-compile/XML.mjs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ StyleAttributeValue1 = function StyleAttributeValue(rules1) {
66
return new StyleAttributeValue.class(rules1);
77
};
88
StyleAttributeValue1.class = class StyleAttributeValue {
9-
#rules;
109
constructor(rules) {
11-
this.#rules = rules;
10+
this.rules = rules;
1211
}
13-
toString() { return "StyleAttributeValue(" + "" + ")"; }
12+
toString() { return "StyleAttributeValue(" + globalThis.Predef.render(this.rules) + ")"; }
1413
};
1514
(class XML {
1615
static {

hkmc2/shared/src/test/mlscript-compile/XML.mls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ open Predef
55
open Iter { mapping, joined }
66

77

8-
class StyleAttributeValue(rules)
8+
data class StyleAttributeValue(rules)
99

1010

1111
module XML with ...

hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -394,21 +394,33 @@ Foo(1, 2, 3).args
394394

395395
:todo
396396
if Foo(1, 2, 3) is Foo(...args) then args
397-
//│ ╔══[ERROR] Unrecognized pattern (spread)
397+
//│ ╔══[ERROR] the constructor does not take any arguments but found 1
398398
//│ ║ l.396: if Foo(1, 2, 3) is Foo(...args) then args
399-
//│ ╙── ^^^^
399+
//│ ╙── ^^^^^^^^^^^^
400+
//│ ╔══[ERROR] Name not found: args
401+
//│ ║ l.396: if Foo(1, 2, 3) is Foo(...args) then args
402+
//│ ╙── ^^^^
400403
//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing
401404

402405
if Foo(1, 2, 3) is Foo(a, b, c) then [a, b, c]
403-
//│ ╔══[ERROR] mismatched arity: expect 1, found 3
404-
//│ ║ l.402: if Foo(1, 2, 3) is Foo(a, b, c) then [a, b, c]
406+
//│ ╔══[ERROR] the constructor does not take any arguments but found 3
407+
//│ ║ l.405: if Foo(1, 2, 3) is Foo(a, b, c) then [a, b, c]
405408
//│ ╙── ^^^^^^^^^^^^
409+
//│ ╔══[ERROR] Name not found: a
410+
//│ ║ l.405: if Foo(1, 2, 3) is Foo(a, b, c) then [a, b, c]
411+
//│ ╙── ^
406412
//│ ╔══[ERROR] Name not found: b
407-
//│ ║ l.402: if Foo(1, 2, 3) is Foo(a, b, c) then [a, b, c]
413+
//│ ║ l.405: if Foo(1, 2, 3) is Foo(a, b, c) then [a, b, c]
408414
//│ ╙── ^
409415
//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing
410416

411417
if Foo(1, 2, 3) is Foo(arg) then arg
418+
//│ ╔══[ERROR] the constructor does not take any arguments but found 1
419+
//│ ║ l.417: if Foo(1, 2, 3) is Foo(arg) then arg
420+
//│ ╙── ^^^^^^^^
421+
//│ ╔══[ERROR] Name not found: arg
422+
//│ ║ l.417: if Foo(1, 2, 3) is Foo(arg) then arg
423+
//│ ╙── ^^^
412424
//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing
413425

414426
// ——— ——— ———

hkmc2/shared/src/test/mlscript/backlog/UCS.mls

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ if 1 + 1
111111

112112
// ——— ——— ———
113113

114-
class Some(v)
114+
class Some(val v)
115115

116116
Some(1) is
117117
Some of [process, rest] do "hi"
@@ -122,6 +122,7 @@ Some(1) is
122122

123123
// ——— ——— ———
124124

125+
class Some(val v)
125126
object None
126127

127128
// :ucs normalized
@@ -159,31 +160,21 @@ fun parse(text: Str): [Int, Str] =
159160

160161
// ——— ——— ———
161162

162-
class
163-
Tuple(a, b, c)
164-
Ident(n)
165-
166-
fun foo(tree) =
167-
fun go(x) = ???
168-
if tree is
169-
Tuple(category, choice, function) and
170-
category is Ident(categoryName) and
171-
function is Ident(functionName) and
172-
choice is
173-
Tuple(elements) then Some(go(elements))
174-
other then Some(go(other))
175-
else
176-
console.warn("expect the choice to be a bracketed term but found " + choice)
177-
None
178-
else
179-
console.warn("expect a the category to be an identifier but found " + category)
180-
None
181-
else
182-
console.warn("expect the definition to be a tuple but found " + tree)
183-
None
163+
data class Tuple(a, b, c)
164+
165+
let foo =
166+
case
167+
Tuple(...elements) then elements
184168
//│ ╔══[ERROR] mismatched arity: expect 3, found 1
185-
//│ ║ l.173: Tuple(elements) then Some(go(elements))
186-
//│ ╙── ^^^^^^^^^^^^^^^
169+
//│ ║ l.167: Tuple(...elements) then elements
170+
//│ ╙── ^^^^^^^^^^^^^^^^^^
171+
//│ ╔══[ERROR] Unrecognized pattern (spread)
172+
//│ ║ l.167: Tuple(...elements) then elements
173+
//│ ╙── ^^^^^^^^
174+
//│ foo = [function]
175+
176+
foo(Tuple(1, 2, 3))
177+
//│ ═══[RUNTIME ERROR] Error: match error
187178

188179
// ——— ——— ———
189180

@@ -196,28 +187,28 @@ fun foo(x) = if x is Foo.
196187
Bar then "Bar"
197188
Foo then "Foo"
198189
//│ ╔══[ERROR] Unrecognized pattern split.
199-
//│ ║ l.195: fun foo(x) = if x is Foo.
190+
//│ ║ l.186: fun foo(x) = if x is Foo.
200191
//│ ╙── ^^^
201192
//│ ╔══[ERROR] Cannot use this identifier as an extractor
202-
//│ ║ l.195: fun foo(x) = if x is Foo.
193+
//│ ║ l.186: fun foo(x) = if x is Foo.
203194
//│ ╙── ^
204195
//│ ╔══[ERROR] Cannot use this identifier as an extractor
205-
//│ ║ l.195: fun foo(x) = if x is Foo.
196+
//│ ║ l.186: fun foo(x) = if x is Foo.
206197
//│ ╙── ^
207198

208199
fun foo(x) = if x is Foo
209200
.Bar then "Bar"
210201
.Foo then "Foo"
211202
//│ ╔══[PARSE ERROR] Expected an expression; found selector instead
212-
//│ ║ l.209: .Bar then "Bar"
203+
//│ ║ l.200: .Bar then "Bar"
213204
//│ ╙── ^^^^
214205
//│ ╔══[PARSE ERROR] Unexpected selector here
215-
//│ ║ l.209: .Bar then "Bar"
206+
//│ ║ l.200: .Bar then "Bar"
216207
//│ ╙── ^^^^
217208
//│ ╔══[ERROR] Unrecognized pattern split.
218-
//│ ║ l.208: fun foo(x) = if x is Foo
209+
//│ ║ l.199: fun foo(x) = if x is Foo
219210
//│ ║ ^^^
220-
//│ ║ l.209: .Bar then "Bar"
211+
//│ ║ l.200: .Bar then "Bar"
221212
//│ ╙── ^^
222213

223214
// ——— ——— ———

hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ case
7777
//│ trm = SynthSel{class:Foo}:
7878
//│ prefix = Ref of member:Foo
7979
//│ nme = Ident of "class"
80-
//│ parameters = S of Ls of
81-
//│ $param0
80+
//│ args = S of Ls of
81+
//│ S of $param0
8282
//│ refined = false
8383
//│ continuation = Let:
8484
//│ sym = a

hkmc2/shared/src/test/mlscript/ucs/hygiene/CrossBranchCapture.mls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ process(Numb(1), 10)
2525

2626

2727
// class Vec(xs: Array[Numb | Vec]) // Array is not available
28-
abstract class Vec[out T](n: Int)
28+
data abstract class Vec[out T](n: Int)
2929
data class Cons[out T](head: T, tail: Vec[T]) extends Vec[T]
3030
module Nil extends Vec[Nothing]
3131

0 commit comments

Comments
 (0)