diff --git a/aeneas/src/core/Eval.v3 b/aeneas/src/core/Eval.v3 index 2c3ad6d7c..7181ebbcd 100644 --- a/aeneas/src/core/Eval.v3 +++ b/aeneas/src/core/Eval.v3 @@ -126,6 +126,12 @@ component Eval { return b == null; } CLASS_QUERY, VARIANT_QUERY => { + if (EnumType.?(tt)) { + // enum subtype query: check integer tag is in [tagLo, tagHi] + var decl = EnumType.!(tt).enumDecl; + var tagVal = Int.unbox(val); + return tagVal >= decl.tagLo && tagVal <= decl.tagHi; + } if (val == null) return false; if (tt.open()) return false; return Record.?(val) && TypeSystem.isSubtype(Record.!(val).rtype, tt); @@ -811,12 +817,22 @@ def evalOp(op: Operator, args: Arguments) -> Result { return if(object != null, object.values[field.index]); } VariantGetMethod(method) => { + var ta = args.getTypeArgs(); + if (EnumType.?(ta[0])) { + var spec = lookupEnumVirtual(args, method); + return Closure.new(args.vals[0], spec); + } var object = args.r(0); var spec = args.getClosedIrSpec(method); if (spec == null) return args.notFoldable(null); return Closure.new(object, spec); } VariantGetVirtual(method) => { + var ta = args.getTypeArgs(); + if (EnumType.?(ta[0])) { + var spec = lookupEnumVirtual(args, method); + return Closure.new(args.vals[0], spec); + } var object = getRecordReceiver(args); var spec = args.getClosedIrSpec(method); if (spec == null) return args.notFoldable(null); @@ -824,6 +840,11 @@ def evalOp(op: Operator, args: Arguments) -> Result { return Closure.new(object, spec); } VariantGetSelector(selector) => { + var ta = args.getTypeArgs(); + if (EnumType.?(ta[0])) { + var spec = lookupEnumVirtual(args, selector); + return FuncVal.new(spec); + } var object = getRecordReceiver(args); var spec = args.getClosedIrSpec(selector); if (spec == null) return args.notFoldable(null); @@ -832,8 +853,9 @@ def evalOp(op: Operator, args: Arguments) -> Result { } //---------------------------------------------------------------------------- NullCheck => { - var object = args.r(0); - if (object == null) return args.throw(V3Exception.NullCheck, null); + var val = args.vals[0]; + if (val == null) return args.throw(V3Exception.NullCheck, null); + if (!Record.?(val)) return Values.BOTTOM; // non-ref values (e.g. enums) are never null return Values.BOTTOM; } BoundsCheck => { @@ -915,6 +937,11 @@ def evalOp(op: Operator, args: Arguments) -> Result { return args.tailCall(spec, object, 1, args.vals.length); } CallVariantVirtual(method) => { + var ta = args.getTypeArgs(); + if (EnumType.?(ta[0])) { + var spec = lookupEnumVirtual(args, method); + return args.tailCall(spec, args.vals[0], 1, args.vals.length); + } var object = getRecordReceiver(args); var spec = args.getClosedIrSpec(method); if (spec == null) return args.notFoldable(null); @@ -922,6 +949,11 @@ def evalOp(op: Operator, args: Arguments) -> Result { return args.tailCall(spec, object, 1, args.vals.length); } CallVariantSelector(selector) => { + var ta = args.getTypeArgs(); + if (EnumType.?(ta[0])) { + var spec = lookupEnumVirtual(args, selector); + return args.tailCall(spec, args.vals[0], 1, args.vals.length); + } var object = getRecordReceiver(args); var spec = args.getClosedIrSpec(selector); if (spec == null) return args.notFoldable(null); @@ -1148,6 +1180,19 @@ def lookupClassVirtual(args: Arguments, object: Record, spec: IrSpec) -> IrSpec args.throw(V3Exception.NullCheck, null); return null; } +def lookupEnumVirtual(args: Arguments, member: IrMember) -> IrSpec { + var ta = args.getTypeArgs(); + var baseSpec = IrSpec.new(ta[0], ta, member); + var tag = Int.unbox(args.vals[0]); + if (IrMethod.?(member)) { + var rootVst = IrMethod.!(member).source; + if (rootVst != null && rootVst.enumCaseIrs != null && tag < rootVst.enumCaseIrs.length) { + var overrideIr = rootVst.enumCaseIrs[tag]; + if (overrideIr != null) return IrSpec.new(ta[0], ta, overrideIr); + } + } + return baseSpec; +} def lookupVariantVirtual(args: Arguments, object: Record, spec: IrSpec) -> IrSpec { if (object != null) return args.getProgram().ir.resolveMethodImpl(object.rtype, spec); return args.getProgram().ir.resolveVariantDefaultMethodImpl(spec); diff --git a/aeneas/src/core/Operator.v3 b/aeneas/src/core/Operator.v3 index de91b8bdb..89c9c28e2 100644 --- a/aeneas/src/core/Operator.v3 +++ b/aeneas/src/core/Operator.v3 @@ -417,6 +417,12 @@ component V3Op { var paramTypes = Arrays.prepend(ftype, Function.getParamTypeArray(ftype)); return newOp0(Opcode.CallFunction, [ftype], paramTypes, Function.getReturnType(ftype)); } + def newCallFunctionDirect(ftype: Type) -> Operator { + ftype = Function.funcRefType(ftype); + if (ftype.typeCon.kind != Kind.FUNCREF) return V3.fail("only function types allowed"); + var paramTypes = Arrays.prepend(ftype, Function.getParamTypeArray(ftype)); + return newOp0(Opcode.CallFunction, [ftype], paramTypes, Function.getReturnType(ftype)); + } def newCreateClosure(methodRef: IrSpec, closure: Type) -> Operator { var typeArgs = methodRef.typeArgs; return newOp0(Opcode.CreateClosure(methodRef.asMethod()), typeArgs, [closure], methodRef.getBoundType()); diff --git a/aeneas/src/debug/Dwarf.v3 b/aeneas/src/debug/Dwarf.v3 index 4d923b52e..a8b2ae418 100644 --- a/aeneas/src/debug/Dwarf.v3 +++ b/aeneas/src/debug/Dwarf.v3 @@ -366,7 +366,7 @@ class DwarfInfoSection(abbrev: DwarfAbbrevSection) { c.render(buf); buf.send(w.putr); w.putb(0); - w.put_uleb32(u32.!(c.tag)); + w.put_uleb32(u32.!(c.localTag)); } w.putb(0); } diff --git a/aeneas/src/ir/Normalization.v3 b/aeneas/src/ir/Normalization.v3 index 8a00a8415..fa0235ea9 100644 --- a/aeneas/src/ir/Normalization.v3 +++ b/aeneas/src/ir/Normalization.v3 @@ -158,6 +158,10 @@ class ReachabilityNormalizer(config: NormalizerConfig, ra: ReachabilityAnalyzer) if (rc.isUnboxed()) { // move flattened data type receiver to function sig ftype = Function.prependParamTypes(rc.variantNorm.sub, ftype); + } else if (EnumType.?(rc.oldType)) { + // enum: prepend tag type so dispatch table CallFunction matches CallMethod + var tagType = V3.getVariantTagType(rc.oldType); + ftype = Function.prependParamType(tagType, ftype); } rm.funcNorm = FuncNorm.!(norm(ftype)); var typeParams = if(rm.spec != null, rm.spec.getTypes().methodTypeArgs); @@ -326,7 +330,7 @@ class ReachabilityNormalizer(config: NormalizerConfig, ra: ReachabilityAnalyzer) // liveClasses, so layoutMtable can filter them out. def numberVariantSlot(c: RaClass, rootMin: int) { var decl = ClassType.!(c.oldType).classDecl; - var tagLo = decl.variantTag, tagHi = decl.variantTagHi; + var tagLo = decl.variantTag, tagHi = decl.tagHi; c.minClassId = rootMin + tagLo; c.maxClassId = rootMin + tagHi + 1; liveClasses.grow(c.maxClassId); @@ -588,11 +592,15 @@ class ReachabilityNormalizer(config: NormalizerConfig, ra: ReachabilityAnalyzer) rm.norm.flags |= IrFlag.M_OVERRIDE; sm.norm.flags |= IrFlag.M_OVERRIDDEN; } + // For enum methods, M_OVERRIDDEN must be transferred from original + // because enum case overrides don't share vtable slots. + if (m.flags.M_OVERRIDDEN) rm.norm.flags |= IrFlag.M_OVERRIDDEN; if (rm.virtual != null) virtuals = List.new(rm.virtual, virtuals); } def layoutMtable(rv: RaVirtual) { if (rv.mtable != null) return; var rm = rv.raMethod, rc = ra.getClass(rm.receiver); + if (EnumType.?(rc.oldType)) return layoutEnumMtable(rv, rm, rc); var size = rc.maxClassId - rc.minClassId; if (ra.compiler.RaDevirtualize && size == 1) return; // no need for an mtable var table = Array.new(size), mtable = IrMtable.new(rm.norm, rc.minClassId, table); @@ -630,6 +638,37 @@ class ReachabilityNormalizer(config: NormalizerConfig, ra: ReachabilityAnalyzer) setMtable(l.head, rv); } } + def layoutEnumMtable(rv: RaVirtual, rm: RaMethod, rc: RaClass) { + var enumDecl = EnumType.!(rc.oldType).enumDecl; + var numCases = enumDecl.tagHi + 1; + var table = Array.new(numCases); + var mtable = IrMtable.new(rm.norm, 0, table); + rv.mtable = mtable; + + var ft = Function.funcRefType(rm.norm.getMethodType()); + mtable.record = ra.prog.newRecord(V3Array.newType(ft), numCases); + + // Fill all slots with the default implementation. + var defaultSpec = IrSpec.new(rm.norm.receiver, [rm.norm.receiver], rm.norm); + for (i < numCases) { + table[i] = rm.norm; + mtable.record.values[i] = FuncVal.new(defaultSpec); + } + + // Fill override slots. + var rootVst = rm.orig.source; + if (rootVst != null && rootVst.enumCaseIrs != null) { + for (i < rootVst.enumCaseIrs.length) { + var overrideIr = rootVst.enumCaseIrs[i]; + if (overrideIr == null) continue; + var overrideRm = overrideIr.raMethod; + if (overrideRm == null || !overrideRm.raFacts.RM_LIVE) continue; + table[i] = overrideRm.norm; + var ta = Arrays.replace(overrideRm.getSpec().typeArgs, 0, overrideRm.norm.receiver); + mtable.record.values[i] = FuncVal.new(IrSpec.new(ta[0], ta, overrideRm.norm)); + } + } + } def resolveMethodImpl(rc: RaClass, rm: RaMethod) -> RaMethod { var sm: RaMethod; for (sc = rc; sc != null; sc = sc.parent) { // find super method, if any diff --git a/aeneas/src/ir/Reachability.v3 b/aeneas/src/ir/Reachability.v3 index fa38dcb66..c74cf161a 100644 --- a/aeneas/src/ir/Reachability.v3 +++ b/aeneas/src/ir/Reachability.v3 @@ -524,10 +524,31 @@ class ReachabilityAnalyzer(compilation: Compilation) { if (rm.isVirtual()) return; rm.virtual = RaVirtual.new(rm); var rc = makeClass(rm.receiver); + if (EnumType.?(rc.oldType)) { + getEnumVirtual(rm, rc); + return; + } for (l = rc.subtypes; l != null; l = l.tail) { analyzeVirtual(l.head, rm); } } + def getEnumVirtual(rm: RaMethod, rc: RaClass) { + // Add root implementation and mark it live so its SSA is generated. + var rv = rm.virtual; + rv.addImpl(rm); + getMethod(null, rm); + // Add per-case override implementations. + var rootVst = rm.orig.source; + if (rootVst != null && rootVst.enumCaseIrs != null) { + for (ir in rootVst.enumCaseIrs) { + if (ir == null) continue; + var overrideRm = makeMethod([rm.receiver], ir, null); + ir.raMethod = overrideRm; + rv.addImpl(overrideRm); + getMethod(null, overrideRm); + } + } + } def getMethod(op: SsaApplyOp, rm: RaMethod) { if (rm.setFact(RaFact.RM_LIVE)) return; liveMethods.put(rm); @@ -728,6 +749,9 @@ class ReachabilityAnalyzer(compilation: Compilation) { if (ic == null) ic = IrClass.new(t, null, null, [], []); raType = newRaClass(t, ic, parent); } + ENUM => { + raType = newRaClass(t, oldIr.makeIrClass(t), null); + } _ => { for (l = t.nested; l != null; l = l.tail) makeType(l.head); raType = RaType.new(t); diff --git a/aeneas/src/ir/SsaNormalizer.v3 b/aeneas/src/ir/SsaNormalizer.v3 index b0bd07761..93ef46587 100644 --- a/aeneas/src/ir/SsaNormalizer.v3 +++ b/aeneas/src/ir/SsaNormalizer.v3 @@ -59,6 +59,7 @@ class SsaRaNormalizer extends SsaRebuilder { newParams.put(SsaParam.new(newParams.length, newIrType)); start++; // skip synthesized receiver } + // enum: receiver maps directly to the prepended tag param (no synthesized receiver) } if (tn.size == 1) { // common case; simple normalization @@ -253,6 +254,8 @@ class SsaRaNormalizer extends SsaRebuilder { ai_new = Arrays.prepend(newGraph.nullReceiver(), ai_new); } } + if (EnumType.?(rc.oldType)) i_old.facts |= Fact.O_NO_NULL_CHECK; + // enum: no receiver prepend needed (tag is already in normalized args) normCall(i_old, funcNorm, newOp, ai_new); } CallClassMethod(method) => { @@ -287,6 +290,10 @@ class SsaRaNormalizer extends SsaRebuilder { CallVariantVirtual(method) => { // devirtualize methods that are not overridden var rc = norm.ra.getClass(op.typeArgs[0]); + if (EnumType.?(rc.oldType)) { + i_old.facts |= Fact.O_NO_NULL_CHECK; + return normEnumVirtualCall(i_old, orig, method); + } var t = extractVirtualRef(orig, method), funcNorm = t.0, m = t.1; var ai_new = normArgs(funcNorm, genRefs(i_old.inputs)); if (t.2) { // still a virtual dispatch @@ -854,6 +861,30 @@ class SsaRaNormalizer extends SsaRebuilder { ifc.endCase(newGraph.falseConst()); return ifc.finish(); } + def normEnumVirtualCall(i_old: SsaApplyOp, orig: Operator, method: IrMethod) { + var t = extractVirtualRef(orig, method), funcNorm = t.0, m = t.1; + var ai_new = normArgs(funcNorm, genRefs(i_old.inputs)); + var tagType = V3.getVariantTagType(EnumType.!(norm.ra.getClass(orig.typeArgs[0]).oldType)); + if (t.2) { // still a virtual dispatch + // enum value IS the tag; use it to index into the dispatch table + var tag = ai_new[0]; + var record = IrSelector.!(m.member).mtable.record; + var table = newGraph.valConst(record.rtype, record); + var func = curBlock.opArrayGetElem(record.rtype, tagType, Facts.O_SAFE_BOUNDS, table, tag); + if (norm.config.NonRefClosureReceiver) { + // Native/wasm: no Oop prepend + ai_new = Arrays.prepend(func, ai_new); + normCall(i_old, funcNorm, V3Op.newCallFunctionDirect(funcNorm.sub[0]), ai_new); + } else { + // JVM: prepend null Oop for closure compatibility + ai_new = Arrays.concat([func, newGraph.nullReceiver()], ai_new); + normCall(i_old, funcNorm, V3Op.newCallFunction(funcNorm.sub[0]), ai_new); + } + } else { + // devirtualized: no receiver prepend (tag is already in normalized args) + normCall(i_old, funcNorm, V3Op.newCallMethod(m), ai_new); + } + } def normVariantGetTag(vn: VariantNorm, args: Range) -> SsaInstr { if (vn == null) return null; if (vn.hasNoTag()) { @@ -1086,6 +1117,23 @@ class SsaRaNormalizer extends SsaRebuilder { } return opAnd(left, check); } + if (EnumType.?(atn.oldType) && EnumType.?(rtn.oldType)) { + // Enum subtype range test: lo <= tag <= hi. + var aDecl = EnumType.!(atn.oldType).enumDecl; + var rDecl = EnumType.!(rtn.oldType).enumDecl; + var actualTag = ai_old[offset]; // enum value IS its tag integer + var tagType = IntType.!(aDecl.tagType); + var lo = rDecl.tagLo, hi = rDecl.tagHi; + var check: SsaInstr; + if (lo == hi) { + check = curBlock.pure(V3Op.newIntEq(tagType), [actualTag, newGraph.intConst(lo)]); + } else { + var chkLo = curBlock.pure(tagType.opLtEq(), [newGraph.intConst(lo), actualTag]); + var chkHi = curBlock.pure(tagType.opLtEq(), [actualTag, newGraph.intConst(hi)]); + check = curBlock.opBoolAnd0(chkLo, chkHi); + } + return opAnd(left, check); + } // break } _ => ; // break @@ -1742,6 +1790,7 @@ class SsaRaNormalizer extends SsaRebuilder { return vals; } def normNullCheck(oldApp: SsaApplyOp, op: Operator) { + if (EnumType.?(op.typeArgs[0])) return; // enum values are never null var newArgs = genRefs(oldApp.inputs); if (newArgs.length >= 1) addNullCheck(oldApp, newArgs[0]); } diff --git a/aeneas/src/ir/VariantNormalizer.v3 b/aeneas/src/ir/VariantNormalizer.v3 index a8c4bdc36..7a9f63fc1 100644 --- a/aeneas/src/ir/VariantNormalizer.v3 +++ b/aeneas/src/ir/VariantNormalizer.v3 @@ -276,7 +276,7 @@ class VariantNormalizer(nc: NormalizerConfig, rn: ReachabilityNormalizer, verbos var vn = VariantNorm.new(child.oldType, newType, solution.types, child.variantFields, tagField); vn.tagValue = V3.getVariantTag(child.oldType); - vn.tagHi = ClassType.!(child.oldType).classDecl.variantTagHi; + vn.tagHi = ClassType.!(child.oldType).classDecl.tagHi; child.variantNorm = vn; parentNorm.children = List.new(vn, parentNorm.children); @@ -297,7 +297,7 @@ class VariantNormalizer(nc: NormalizerConfig, rn: ReachabilityNormalizer, verbos if (child.variantNorm != null) continue; // already handled by main loop var vn = VariantNorm.new(child.oldType, newType, types, NO_FIELDS, tagField); vn.tagValue = V3.getVariantTag(child.oldType); - vn.tagHi = ClassType.!(child.oldType).classDecl.variantTagHi; + vn.tagHi = ClassType.!(child.oldType).classDecl.tagHi; child.variantNorm = vn; parentNorm.children = List.new(vn, parentNorm.children); createSubtypeVariantNorms(child, vn, newType, types, tagField); @@ -306,7 +306,7 @@ class VariantNormalizer(nc: NormalizerConfig, rn: ReachabilityNormalizer, verbos private def unboxUsingEnumVariantNorm(rc: RaClass, tagType: IntType, tagField: VariantField) { var vn = VariantNorm.new(rc.oldType, tagType, [tagType], NO_FIELDS, tagField); vn.tagValue = V3.getVariantTag(rc.oldType); - vn.tagHi = ClassType.!(rc.oldType).classDecl.variantTagHi; + vn.tagHi = ClassType.!(rc.oldType).classDecl.tagHi; rc.raFacts |= RaFact.RC_ENUM; rc.variantNorm = vn; diff --git a/aeneas/src/ir/VstIr.v3 b/aeneas/src/ir/VstIr.v3 index ad83d1d16..c462ca5fc 100644 --- a/aeneas/src/ir/VstIr.v3 +++ b/aeneas/src/ir/VstIr.v3 @@ -68,11 +68,87 @@ class IrBuilder(ctype: Type, parent: IrClass) { else if (VstNew.?(m)) addVstNew(VstNew.!(m), isVariant, unboxed); else if (VstMethod.?(m)) addVstMethod(VstMethod.!(m)); } + // Build IR methods for per-case enum method overrides. + if (decl.isEnum()) addEnumCaseOverrides(VstClass.!(decl)); var ic = build(); ic.boxing = boxing; ic.packed = packed; return ic; } + def addEnumCaseOverrides(decl: VstClass) { + // For subtype enums, all overrides are already processed by the + // root enum's buildClass (see addSubtypeOverridesRecursive below). + if (decl.parentEnum != null) return; + // Process root's own per-case overrides. + addEnumCaseOverridesForDecl(decl); + // Recursively process all subtypes so that M_OVERRIDDEN is set + // on root methods and enumCaseIrs is populated before any SSA + // generation (critical for v3i lazy evaluation). + addSubtypeOverridesRecursive(decl); + } + def addSubtypeOverridesRecursive(decl: VstClass) { + for (l = decl.subtypeOrder; l != null; l = l.tail) { + addEnumCaseOverridesForDecl(l.head); + addSubtypeOverridesRecursive(l.head); + } + } + def addEnumCaseOverridesForDecl(decl: VstClass) { + var rootEnum = findRootEnum(decl); + var tableSize = rootEnum.tagHi + 1; + // 1. Subtype-level method overrides: fill range in root's enumCaseIrs. + for (l = decl.members; l != null; l = l.tail) { + if (!VstMethod.?(l.head)) continue; + var vm = VstMethod.!(l.head); + if (vm.root == null || vm.root == vm) continue; // not an override + var ir = newIrMethod(vm); + addIrMethod(ir); + vm.index = ir.index; + ir.flags |= IrFlag.M_OVERRIDE; + var rootVst = vm.root; + markEnumRootMethodOverridden(rootVst); + if (rootVst.enumCaseIrs == null) { + rootVst.enumCaseIrs = Array.new(tableSize); + } + for (i = decl.tagLo; i <= decl.tagHi; i++) { + rootVst.enumCaseIrs[i] = ir; + } + } + // 2. Per-case method overrides. + for (c in decl.cases) { + if (c.members == null) continue; + for (l = c.members; l != null; l = l.tail) { + if (!VstMethod.?(l.head)) continue; + var vm = VstMethod.!(l.head); + var ir = newIrMethod(vm); + addIrMethod(ir); + vm.index = ir.index; + ir.flags |= IrFlag.M_OVERRIDE; + var rootVst = vm.root; + markEnumRootMethodOverridden(rootVst); + if (rootVst.enumCaseIrs == null) { + rootVst.enumCaseIrs = Array.new(tableSize); + } + var tag = c.enumTag; + rootVst.enumCaseIrs[tag] = ir; + } + } + } + def markEnumRootMethodOverridden(rootVst: VstMethod) { + // Find the root method's IrMethod in the current builder's methods vector. + // We search by source reference since the VstMethod.index may not yet + // reflect the IrMethod index (it's updated by addVstMethod). + for (i = 2; i < methods.length; i++) { + var m = methods[i]; + if (m != null && m.source == rootVst) { + m.flags |= IrFlag.M_OVERRIDDEN; + return; + } + } + } + def findRootEnum(decl: VstClass) -> VstClass { + while (decl.parentEnum != null) decl = decl.parentEnum; + return decl; + } def addVstField(f: VstField, isVariant: bool, unboxed: bool) { var ir = IrField.new(ctype, f.getType()); addRepHintFacts(f, ir); @@ -108,12 +184,15 @@ class IrBuilder(ctype: Type, parent: IrClass) { if (r == m) { // this is the root method of a method family addIrMethod(ir); + } else if (parent != null) { + // this is a class/variant override + parent.methods[r.index].flags |= IrFlag.M_OVERRIDDEN; + ir.flags |= IrFlag.M_OVERRIDE; + setIrMethod(r.index, ir); } else { - // this is an override - parent.methods[r.index].flags |= IrFlag.M_OVERRIDDEN; - ir.flags |= IrFlag.M_OVERRIDE; - setIrMethod(r.index, ir); - } + // enum subtype override: handled by addEnumCaseOverrides + addIrMethod(ir); + } m.index = ir.index; } def newIrMethod(m: VstMethod) -> IrMethod { diff --git a/aeneas/src/jvm/JvmGen.v3 b/aeneas/src/jvm/JvmGen.v3 index 5f5fca38c..f8ede8d0d 100644 --- a/aeneas/src/jvm/JvmGen.v3 +++ b/aeneas/src/jvm/JvmGen.v3 @@ -107,6 +107,9 @@ class JvmProgram(compiler: Compiler, prog: Program) { } CLASS => jvmType(utype); COMPONENT => jvmType(utype); + ENUM => { + if (ic.methods.length > 0) jrep.makeJvmEnumMethodHost(utype, ic); + } _ => ; } } @@ -162,7 +165,9 @@ class JvmProgram(compiler: Compiler, prog: Program) { var buffer = StringBuilder.new(); buffer.puts(mangleTypeName("V3D_", memberRef.receiver)); buffer.puts("$D"); - var methName = V3.mangleIrMember(memberRef.member); + var methName = if(EnumType.?(memberRef.receiver), + mangleEnumMethod(IrMethod.!(memberRef.member)), + V3.mangleIrMember(memberRef.member)); buffer.puts(methName); return buffer.toString(); } @@ -250,6 +255,10 @@ class JvmProgram(compiler: Compiler, prog: Program) { } return abstractClosureClass; } + def mangleEnumMethod(m: IrMethod) -> string { + // Append uid to distinguish overrides that share the same method name. + return StringBuilder.new().puts(V3.mangleIrMember(m)).putc('$').putd(m.uid).toString(); + } def isUseless(vtype: Type) -> bool { return vtype == Void.TYPE || V3.isComponent(vtype); } @@ -480,6 +489,22 @@ class JvmV3ComponentGen extends JvmClassGen { main_code.attach(main_meth); } } +// Generates a Jvm class to host static methods for an enum type +class JvmV3EnumGen extends JvmClassGen { + def enumType: Type; + def ic: IrClass; + new(builder: JvmClassfileBuilder, enumType, ic) super(builder) { } + def build() { + for (m in ic.methods) buildMethod(m); + addEmptyInit(); + } + def buildMethod(m: IrMethod) { + if (m == null) return; + var name = builder.jprog.mangleEnumMethod(m); + var jsig = jvmSig(m.getMethodType()); + genCode(builder, m, name, jsig, true); + } +} // Generates a Jvm class representing a tuple class JvmV3TupleGen extends JvmClassGen { def tupleType: Type; @@ -575,7 +600,7 @@ class JvmV3ClosureGen extends JvmClassGen { if (memberRef == null) { buildAbstractClass(invoke_meth); } else { - if (V3.isComponent(memberRef.receiver)) buildComponentClosure(invoke_meth); + if (V3.isComponent(memberRef.receiver) || EnumType.?(memberRef.receiver)) buildComponentClosure(invoke_meth); else buildClassClosure(invoke_meth); } } @@ -626,7 +651,10 @@ class JvmV3ClosureGen extends JvmClassGen { builder.emitLoadArguments(invoke_code, methType, methType, 2); var jclass = builder.jprog.jvmClass(memberRef.receiver); invoke_code.locals(invoke_sig.localsSize(true)); - invoke_code.invokestatic(jclass.name, V3.mangleIrMember(memberRef.member), jvmSig(methType)); + var mname = if(EnumType.?(memberRef.receiver), + builder.jprog.mangleEnumMethod(IrMethod.!(memberRef.member)), + V3.mangleIrMember(memberRef.member)); + invoke_code.invokestatic(jclass.name, mname, jvmSig(methType)); invoke_code.ret(jvmKind(Function.getReturnType(funcType))); invoke_code.attach(invoke_meth); } diff --git a/aeneas/src/jvm/JvmRep.v3 b/aeneas/src/jvm/JvmRep.v3 index d7f1b84c1..5f4fb1410 100644 --- a/aeneas/src/jvm/JvmRep.v3 +++ b/aeneas/src/jvm/JvmRep.v3 @@ -99,6 +99,12 @@ class JvmTypeReps(jprog: JvmProgram) { jprog.classQueue.put(JvmV3ClosureGen.new(JvmClassfileBuilder.new(jprog, jclass), utype, null, 0)); return jclass; } + def makeJvmEnumMethodHost(utype: Type, ic: IrClass) { + var className = jprog.mangleTypeName("V3E_", utype); + var jclass = JvmClass.new(className, JvmTypes.jlo); + jvmClasses[utype] = jclass; + jprog.classQueue.put(JvmV3EnumGen.new(JvmClassfileBuilder.new(jprog, jclass), utype, ic)); + } def emitGetField(memberRef: IrSpec, code: JvmCodeBuilder) { var fieldType = memberRef.getFieldType(); var r = getRep(fieldType); diff --git a/aeneas/src/jvm/SsaJvmGen.v3 b/aeneas/src/jvm/SsaJvmGen.v3 index 5ea509a6a..8ff2ab75d 100644 --- a/aeneas/src/jvm/SsaJvmGen.v3 +++ b/aeneas/src/jvm/SsaJvmGen.v3 @@ -667,9 +667,12 @@ class SsaJvmGen(jprog: JvmProgram, context: SsaContext, jsig: JvmSig, code: JvmC if (methodRef.isNew()) { var nsig = jprog.makeJvmSig(jclass, methodRef.getMethodType(), jclass); code.invokestatic(jclass.name, "$init", nsig); - } else if (V3.isComponent(methodRef.receiver)) { + } else if (V3.isComponent(methodRef.receiver) || EnumType.?(methodRef.receiver)) { var nsig = jprog.jvmSig(methodRef.getMethodType()); - code.invokestatic(jclass.name, V3.mangleIrMember(methodRef.member), nsig); + var mname = if(EnumType.?(methodRef.receiver), + jprog.mangleEnumMethod(IrMethod.!(methodRef.member)), + V3.mangleIrMember(methodRef.member)); + code.invokestatic(jclass.name, mname, nsig); } else { jprog.jrep.emitInvokeVirtual(code, methodRef, i.inputs[0].dest.facts.V_NON_ZERO); } diff --git a/aeneas/src/ssa/SsaBuilder.v3 b/aeneas/src/ssa/SsaBuilder.v3 index e3d147cc1..52e61ece4 100644 --- a/aeneas/src/ssa/SsaBuilder.v3 +++ b/aeneas/src/ssa/SsaBuilder.v3 @@ -106,6 +106,23 @@ class SsaBuilder { return opArrayGetElem(V3.stringArrayType, V3.getVariantTagType(t), Facts.O_SAFE_BOUNDS | Fact.O_PURE, array, index); } + VariantGetShortName => { + var t = op.typeArgs[0]; + var record: Record; + if (EnumType.?(t)) record = EnumType.!(t).getShortNames(context.prog); + else record = V3Class_TypeCon.!(t.typeCon).getShortNames(context.prog); + var array = graph.recordConst(record); + var index = if(EnumType.?(t), args[0], add(V3Op.newVariantGetTag(op.typeArgs[0]), args, N)); + return opArrayGetElem(V3.stringArrayType, V3.getVariantTagType(t), + Facts.O_SAFE_BOUNDS | Fact.O_PURE, array, index); + +// var t = EnumType.!(op.typeArgs[0]); +// var record = t.getShortNames(context.prog); +// var array = graph.recordConst(record); +// var tagType = IntType.!(t.enumDecl.tagType); +// return opArrayGetElem(V3.stringArrayType, tagType, +// Facts.O_SAFE_BOUNDS | Fact.O_PURE, array, args[0]); + } EnumGetParam(field) => { var t = field.receiver.declType; var facts = Facts.O_SAFE_BOUNDS; @@ -122,7 +139,7 @@ class SsaBuilder { } EnumSetHas(vcase) => { var etc = EnumSetType.!(pt[0]), it = etc.repType; - var val: Val = if(it.width <= 32, Int.box(1 << u5.!(vcase.tag)), Long.box(1L << u6.!(vcase.tag))); + var val: Val = if(it.width <= 32, Int.box(1 << u5.!(vcase.enumTag)), Long.box(1L << u6.!(vcase.enumTag))); var mask = graph.valConst(it, val); var and = add(it.opAnd(), [args[0], mask], N); return add(it.opEq(), [and, mask], N); diff --git a/aeneas/src/ssa/SsaOptimizer.v3 b/aeneas/src/ssa/SsaOptimizer.v3 index 8dee12aa5..ba39bfffd 100644 --- a/aeneas/src/ssa/SsaOptimizer.v3 +++ b/aeneas/src/ssa/SsaOptimizer.v3 @@ -408,6 +408,7 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { var res = reduceApplyOp(x); if (res != x) { x.replace(res); + x.kill(); x.remove(); } return res; @@ -870,7 +871,7 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { var xval = unop(i); if (xconst) { // ClassGetMethod[m](#K) => (m, #K) - var obj = Record.!(xval); + var obj: Val = if(EnumType.?(meth.receiver), xval, Record.!(xval)); return graph.valConst(meth.getBoundType(), Closure.new(obj, meth)); } } @@ -882,7 +883,7 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { if (dv != null) { var xval = unop(i); if (xconst && !i.op.isPolymorphic()) { // ClassGetVirtual[m](K) => (m, K) - var obj = Record.!(xval); + var obj: Val = if(EnumType.?(meth.receiver), xval, Record.!(xval)); return graph.valConst(dv.getBoundType(), Closure.new(obj, dv)); } return replaceOp(i, V3Op.newClassGetMethod(dv)); // ClassGetVirtual[m](K) => ClassGetMethod[m](K) @@ -1021,7 +1022,8 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { } CallMethod(method) => { i.facts |= method.facts & Fact.O_PURE; - if (i.inputs.length > 0 && i.input0().facts.V_NON_ZERO) i.facts |= Fact.O_NO_NULL_CHECK; + if (EnumType.?(method.receiver)) i.facts |= Fact.O_NO_NULL_CHECK; + else if (i.inputs.length > 0 && i.input0().facts.V_NON_ZERO) i.facts |= Fact.O_NO_NULL_CHECK; state.kill(); } CallClassMethod(method) => { @@ -1044,7 +1046,8 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { } CallVariantVirtual(method) => { var receiver = i.input0(), meth = V3Op.extractIrSpec(i.op, method); - if (i.input0().facts.V_NON_ZERO) i.facts |= Fact.O_NO_NULL_CHECK; + if (EnumType.?(method.receiver)) i.facts |= Fact.O_NO_NULL_CHECK; + else if (i.input0().facts.V_NON_ZERO) i.facts |= Fact.O_NO_NULL_CHECK; var dv = devirtualize(meth, receiver); state.kill(); if (dv != null) return replaceOp(i, V3Op.newCallMethod(dv)); // CallVariantVirtual[m](K) => CallMethod[m](k) @@ -1078,12 +1081,28 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { i_recvr = x.input0(); newFacts = i.facts & Fact.O_NO_NULL_CHECK; } + VariantGetMethod(method) => { + // CallClosure(VariantGetMethod[m](e)) => CallMethod[m](e) + var spec = V3Op.extractIrSpec(xop, method); + newOp = V3Op.newCallMethod(spec); + i_recvr = x.input0(); + if (EnumType.?(spec.receiver)) newFacts |= Fact.O_NO_NULL_CHECK; + else newFacts = i.facts & Fact.O_NO_NULL_CHECK; + } + VariantGetVirtual(method) => { + // CallClosure(VariantGetVirtual[m](e)) => CallVariantVirtual[m](e) + var spec = V3Op.extractIrSpec(xop, method); + newOp = V3Op.newCallVariantVirtual(spec); + i_recvr = x.input0(); + if (EnumType.?(spec.receiver)) newFacts |= Fact.O_NO_NULL_CHECK; + else newFacts = i.facts & Fact.O_NO_NULL_CHECK; + } CreateClosure(method) => { // CallClosure(CreateClosure[m](p), ...) => CallMethod[m](p, ...); var spec = V3Op.extractIrSpec(xop, method); newOp = V3Op.newCallMethod(spec); i_recvr = if (V3.isComponent(spec.receiver), graph.nullReceiver(), x.input0()); - if (V3.isComponent(spec.receiver)) newFacts |= Fact.O_NO_NULL_CHECK; + if (V3.isComponent(spec.receiver) || EnumType.?(spec.receiver)) newFacts |= Fact.O_NO_NULL_CHECK; else newFacts |= x.facts & Fact.O_NO_NULL_CHECK; } _ => { @@ -1449,6 +1468,8 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { def devirtualize(m: IrSpec, x: SsaInstr) -> IrSpec { // Try to devirtualize based on the class hierachy first. if (!m.member.flags.M_OVERRIDDEN) return m; + // Enum types cannot be devirtualized by record/allocation pattern matching. + if (EnumType.?(m.receiver)) return null; var optag = x.optag(); // Look through TypeSubsumes at a potentially more precise receiver. while (optag == Opcode.TypeSubsume.tag) { diff --git a/aeneas/src/ssa/VstSsaGen.v3 b/aeneas/src/ssa/VstSsaGen.v3 index 78e577a66..f2fee630e 100644 --- a/aeneas/src/ssa/VstSsaGen.v3 +++ b/aeneas/src/ssa/VstSsaGen.v3 @@ -95,8 +95,10 @@ class VstSsaGen extends VstVisitor { def addConstructorCode(decl: VstNew, env: VstSsaEnv) { var isClass: bool; match (decl.receiver) { - x: VstEnum => return addEnumParamInitCode(x, env); - x: VstClass => isClass = true; + x: VstClass => { + if (x.isEnum()) return addEnumParamInitCode(x, env); + isClass = true; + } } // Append implicit field initializing from params. for (plist = decl.func.params.list; plist != null; plist = plist.tail) { @@ -140,25 +142,62 @@ class VstSsaGen extends VstVisitor { } } } - def addEnumParamInitCode(receiver: VstEnum, env: VstSsaEnv) { + def addEnumParamInitCode(receiver: VstClass, env: VstSsaEnv) { context.method.flags |= IrFlag.M_ENUM_INIT; - var params = receiver.params.asArray(); - var types = Array.new(params.length); - var consts = Array.new(params.length); - for (i < params.length) { - var r = VstField.!(params[i].member).enumVals; - consts[i] = context.graph.valConst(r.rtype, r); - types[i] = r.rtype; - } - var cases = receiver.cases; - for (i < cases.length) { - var c = cases[i], args = c.args.asArray(); - for (j < args.length) { - var val = env.genExpr(args[j]); - var index = context.graph.intConst(i); - env.opArraySetElem(types[j], Int.TYPE, Facts.O_SAFE_BOUNDS, consts[j], index, val); + // Find the root enum. + var rootEnum = receiver; + while (rootEnum.qualifiers != null) { + var tc = context.prog.typeEnv.lookup(rootEnum.qualifiers.head.image); + if (tc == null || tc.kind != Kind.ENUM) break; + rootEnum = EnumType.!(tc.create0()).enumDecl; + } + // Count total params: root + all ancestor extras + own extras + var totalParams = 0; + if (rootEnum.params != null) totalParams += rootEnum.params.length(); + // Collect ancestor extras in root-to-receiver order + var ancestorExtras: List>; + var cur = receiver; + while (cur != null && cur != rootEnum) { + if (cur.extraParams != null) ancestorExtras = List.new(cur.extraParams, ancestorExtras); + cur = cur.parentEnum; + } + // ancestorExtras is now in root-to-receiver order (reversed during walk) + for (el = ancestorExtras; el != null; el = el.tail) totalParams += el.head.length(); + if (totalParams == 0) return; + var types = Array.new(totalParams); + var consts = Array.new(totalParams); + var offset = 0; + // Root params → root's enumVals arrays + if (rootEnum.params != null) { + var params = rootEnum.params.asArray(); + for (i < params.length) { + var r = VstField.!(params[i].member).enumVals; + consts[offset] = context.graph.valConst(r.rtype, r); + types[offset] = r.rtype; + offset++; } } + // Ancestor extras → each ancestor's own enumVals arrays + for (el = ancestorExtras; el != null; el = el.tail) { + for (l = el.head.list; l != null; l = l.tail) { + var r = VstField.!(l.head.member).enumVals; + consts[offset] = context.graph.valConst(r.rtype, r); + types[offset] = r.rtype; + offset++; + } + } + // Process cases: args map positionally to [root, ancestor extras, own extras] + var cases = receiver.cases; + for (i < cases.length) { + var c = cases[i]; + if (Strings.equal(c.name(), "_")) continue; + var args = c.args.asArray(); + for (j < args.length) { + var val = env.genExpr(args[j]); + var index = context.graph.intConst(c.enumTag); + env.opArraySetElem(types[j], Int.TYPE, Facts.O_SAFE_BOUNDS, consts[j], index, val); + } + } } def getThisParam() -> SsaInstr { return graph.params[0]; @@ -568,14 +607,19 @@ class VstSsaGen extends VstVisitor { return null; } def genPatternCond(keyType: Type, cur: SsaBuilder, pat: MatchPattern, origKey: SsaInstr, key: SsaInstr) -> SsaInstr { - if (pat.vdecl != null && pat.variantCase == null) { - var t = pat.vdecl.tref.binding; - if (V3.isVariant(t) && ClassType.!(t).classDecl.qualifiers != null) { - // subtype variant pattern: generate "S.?(origKey)" using the pre-tag-extraction value - var query = V3Op.newTypeQuery(origKey.getType(), t); - return cur.addApply(null, query, [origKey]); - } + if (pat.subtypeType != null) { + // subtype variant pattern: generate "S.?(origKey)" using the pre-tag-extraction value + var origKeyType = origKey.getType(); + var query = V3Op.newTypeQuery(origKeyType, pat.subtypeType); + return cur.addApply(null, query, [origKey]); + } else if (pat.subtypeEnumType != null) { + // subtype enum pattern: generate "E1.More.?(origKey)" using a VARIANT_QUERY range test + // Must use origKey's enum type (not the tagType) so TypeQuery resolves to VARIANT_QUERY + var query = V3Op.newTypeQuery(origKey.getType(), pat.subtypeEnumType); + return cur.addApply(null, query, [origKey]); + } else if (pat.vdecl != null && pat.variantCase == null) { // type case (first grammar rule "id: TypeRef"), generate "T.?(key)" + var t = pat.vdecl.tref.binding; var query = V3Op.newTypeQuery(keyType, t); return cur.addApply(null, query, [key]); } else { @@ -587,17 +631,19 @@ class VstSsaGen extends VstVisitor { def matchPatterns(keyType: Type, pl: List, val: Val) -> bool { while (pl != null) { var pat = pl.head; - if (pat.vdecl != null) { - var t = pat.vdecl.tref.binding; - if (V3.isVariant(t) && ClassType.!(t).classDecl.qualifiers != null) { - // subtype pattern: check if integer tag falls in the subtype's DFS range - var decl = ClassType.!(t).classDecl; - var tagVal = Int.unbox(val); - if (tagVal >= decl.variantTag && tagVal <= decl.variantTagHi) return true; - } else { - var r = Eval.doQuery0(keyType, t, val); - if (r) return true; - } + if (pat.subtypeType != null) { + // subtype variant pattern: check if integer tag falls in the subtype's DFS range + var decl = ClassType.!(pat.subtypeType).classDecl; + var tagVal = Int.unbox(val); + if (tagVal >= decl.variantTag && tagVal <= decl.tagHi) return true; + } else if (pat.subtypeEnumType != null) { + // subtype enum pattern: check if integer tag falls in the subtype enum's DFS range + var decl = EnumType.!(pat.subtypeEnumType).enumDecl; + var tagVal = Int.unbox(val); + if (tagVal >= decl.tagLo && tagVal <= decl.tagHi) return true; + } else if (pat.vdecl != null) { + var r = Eval.doQuery0(keyType, pat.vdecl.tref.binding, val); + if (r) return true; } else if (Values.equal(val, pat.val)) { return true; } @@ -693,7 +739,7 @@ class VstSsaGen extends VstVisitor { Void => return env.graph.nop(); Const(val, vtype) => return env.graph.valConst(vtype, val); EnumConst(member) => { - return env.graph.valConst(member.receiver.getDeclaredType(), Int.box(member.tag)); + return env.graph.valConst(member.receiver.getDeclaredType(), Int.box(member.enumTag)); } Local(decl) => return env[decl.ssa]; ObjectMethod(receiver, member, typeArgs) => { diff --git a/aeneas/src/v3/TypeSystem.v3 b/aeneas/src/v3/TypeSystem.v3 index 36f9dcca3..7df7495ca 100644 --- a/aeneas/src/v3/TypeSystem.v3 +++ b/aeneas/src/v3/TypeSystem.v3 @@ -34,7 +34,9 @@ component TypeSystem { // Check if the type {xt} is an enum and the type {yt} is its corresponding set type. def isEnumToSet(xt: Type, yt: Type) -> bool { if (EnumType.?(xt) && EnumSetType.?(yt)) { - return EnumType.!(xt).enumDecl == EnumSetType.!(yt).enumDecl; + var xd = EnumType.!(xt).enumDecl; + var yd = EnumSetType.!(yt).enumDecl; + return xd == yd || isEnumSubtypeOf(xd, yd); } return false; } @@ -115,7 +117,8 @@ component TypeSystem { } } x: EnumType => match (yt) { - y: EnumSetType => if (x.enumDecl == y.enumDecl) return Conversion.PROMOTE_ENUM_TO_SET; + y: EnumSetType => if (x.enumDecl == y.enumDecl || isEnumSubtypeOf(x.enumDecl, y.enumDecl)) return Conversion.PROMOTE_ENUM_TO_SET; + y: EnumType => if (isEnumSubtypeOf(x.enumDecl, y.enumDecl)) return Conversion.SUBSUME_VARIANT; } x: FloatType => match (yt) { y: FloatType => if (x.width <= y.width) return Conversion.PROMOTE_FLOAT_TO_FLOAT; @@ -225,6 +228,14 @@ component TypeSystem { return Conversion.ILLEGAL_EXCEPT_NULL; } } + x: EnumType => match (yt) { + y: EnumType => { + if (isEnumSubtypeOf(y.enumDecl, x.enumDecl)) { + return Conversion.CAST_VARIANT; + } + return Conversion.ILLEGAL; + } + } x: OopType => match (yt) { y: ArrayType => return Conversion.CAST_ARRAY; y: ClassType => return if(V3.isVariant(y), Conversion.CAST_VARIANT, Conversion.CAST_CLASS); @@ -779,3 +790,16 @@ def mergeConversions(a: Conversion, b: Conversion) -> Conversion { } return a; } +// Returns true if {sub} is a (direct or transitive) subtype enum of {sup}. +// Uses the subtypeOrder lists populated by the verifier (no prog access needed). +def isEnumSubtypeOf(sub: VstClass, sup: VstClass) -> bool { + if (sub.qualifiers == null) return false; // root enums are not subtypes + return enumAncestorHasSubtype(sup, sub); +} +def enumAncestorHasSubtype(ancestor: VstClass, target: VstClass) -> bool { + for (l = ancestor.subtypeOrder; l != null; l = l.tail) { + if (l.head == target) return true; + if (enumAncestorHasSubtype(l.head, target)) return true; + } + return false; +} diff --git a/aeneas/src/v3/V3Class.v3 b/aeneas/src/v3/V3Class.v3 index 239910368..b8b2b937e 100644 --- a/aeneas/src/v3/V3Class.v3 +++ b/aeneas/src/v3/V3Class.v3 @@ -5,6 +5,7 @@ class V3Class_TypeCon extends TypeCon { def classDecl: VstClass; var names: Record; + var shortNames: Record; new(classDecl, typeCache: TypeCache) super(classDecl.fullName, classDecl.kind, Lists.length(classDecl.typeParams), typeCache) { @@ -15,13 +16,48 @@ class V3Class_TypeCon extends TypeCon { } def getNames(prog: Program) -> Record { if (names == null) { - names = prog.newRecord(V3.stringArrayType, classDecl.cases.length); - for (vc in classDecl.cases) { - names.values[vc.tag] = Byte.boxString(vc.name(), prog); - } + var total = if(classDecl.tagHi >= 0, classDecl.tagHi + 1, classDecl.cases.length); + names = prog.newRecord(V3.stringArrayType, total); + CaseNames.fillNames(names, classDecl, "", prog); } return names; } + def getShortNames(prog: Program) -> Record { + if (shortNames == null) { + var total = if(classDecl.tagHi >= 0, classDecl.tagHi + 1, classDecl.cases.length); + shortNames = prog.newRecord(V3.stringArrayType, total); + CaseNames.fillShortNames(shortNames, classDecl, prog); + } + return shortNames; + } +} +// Shared helpers for building case name arrays (used by both V3Class_TypeCon and EnumType). +component CaseNames { + def fillNames(rec: Record, decl: VstClass, prefix: string, prog: Program) { + for (c in decl.cases) { + var tag = c.runtimeTag(); + if (tag < 0 || Strings.equal(c.name(), "_")) continue; + var n = if(prefix.length == 0, c.name(), Strings.format2("%s.%s", prefix, c.name())); + rec.values[tag] = Byte.boxString(n, prog); + } + var subtypes = Lists.reverse(decl.subtypeOrder); + for (l = subtypes; l != null; l = l.tail) { + var sub = l.head; + var p = if(prefix.length == 0, sub.name(), Strings.format2("%s.%s", prefix, sub.name())); + fillNames(rec, sub, p, prog); + } + } + def fillShortNames(rec: Record, decl: VstClass, prog: Program) { + for (c in decl.cases) { + var tag = c.runtimeTag(); + if (tag < 0 || Strings.equal(c.name(), "_")) continue; + rec.values[tag] = Byte.boxString(c.name(), prog); + } + var subtypes = Lists.reverse(decl.subtypeOrder); + for (l = subtypes; l != null; l = l.tail) { + fillShortNames(rec, l.head, prog); + } + } } // The type for user-declared classes. // XXX: separate VariantType from ClassType and enum type. @@ -74,4 +110,8 @@ class ClassType extends Type { var types = [Type.!(this)]; return V3Op.newVstSugar(VstOperator.VariantGetName, types, types, V3.stringType); } + def getShortNameOperator() -> Operator { + var types = [Type.!(this)]; + return V3Op.newVstSugar(VstOperator.VariantGetShortName, types, types, V3.stringType); + } } diff --git a/aeneas/src/v3/V3Enum.v3 b/aeneas/src/v3/V3Enum.v3 index 82f059c29..4f7e29ac2 100644 --- a/aeneas/src/v3/V3Enum.v3 +++ b/aeneas/src/v3/V3Enum.v3 @@ -3,39 +3,54 @@ // The type for user-declared enums. class EnumType extends PrimType { - def enumDecl: VstEnum; + def enumDecl: VstClass; var setType: EnumSetType; var names: Record; + var shortNames: Record; new(hash: int, enumDecl, typeCon: TypeCon) super(hash, typeCon, enumDecl.tagType.width, null) { } - def lookupCase(name: string) -> VstEnumCase { + def lookupCase(name: string) -> VstCaseMember { var member = enumDecl.memberMap[name]; - if (VstEnumCase.?(member)) return VstEnumCase.!(member); + if (VstCaseMember.?(member)) return VstCaseMember.!(member); return null; } def getNameOperator() -> Operator { var types: Array = [this]; return V3Op.newVstSugar(VstOperator.VariantGetName, types, types, V3.stringType); } + def getShortNameOperator() -> Operator { + var types: Array = [this]; + return V3Op.newVstSugar(VstOperator.VariantGetShortName, types, types, V3.stringType); + } def enumGetParamOperator(f: VstField) -> Operator { var types: Array = [this]; return V3Op.newVstSugar(VstOperator.EnumGetParam(f), TypeUtil.NO_TYPES, types, f.getType()); } + // Get the qualified names array (e.g. "More.C" for a subtype case). + // Array is indexed by DFS enumTag; size = tagHi + 1. def getNames(prog: Program) -> Record { if (names == null) { - names = prog.newRecord(V3.stringArrayType, enumDecl.cases.length); - for (vc in enumDecl.cases) { - names.values[vc.tag] = Byte.boxString(vc.name(), prog); - } + var total = if(enumDecl.tagHi >= 0, enumDecl.tagHi + 1, enumDecl.cases.length); + names = prog.newRecord(V3.stringArrayType, total); + CaseNames.fillNames(names, enumDecl, "", prog); } return names; } + def getShortNames(prog: Program) -> Record { + if (shortNames == null) { + var total = if(enumDecl.tagHi >= 0, enumDecl.tagHi + 1, enumDecl.cases.length); + shortNames = prog.newRecord(V3.stringArrayType, total); + CaseNames.fillShortNames(shortNames, enumDecl, prog); + } + return shortNames; + } } // The type for user-declared enums' sets. class EnumSetType extends PrimType { - def enumDecl: VstEnum; + def enumDecl: VstClass; def repType: IntType; + var allVal: Val; private var cache: V3EnumSet_OpCache; new(hash: int, enumDecl, repType, typeCon: TypeCon) super(hash, typeCon, repType.width, null) { } diff --git a/aeneas/src/vst/MethodEnv.v3 b/aeneas/src/vst/MethodEnv.v3 index 001527b51..796ec8293 100644 --- a/aeneas/src/vst/MethodEnv.v3 +++ b/aeneas/src/vst/MethodEnv.v3 @@ -156,7 +156,22 @@ class MethodEnv { return VarBinding.None; } def resolveEnumMember(expr: VarExpr, enumType: EnumType, name: string) -> ResolvedMember { - var member = enumType.enumDecl.memberMap[name]; + var decl = enumType.enumDecl; + var member = decl.memberMap[name]; + if (decl.qualifiers != null) { + if (member == null || VstField.?(member)) { + // Walk the ancestor chain for fields and missing members. + var cur = decl.parentEnum; + while (cur != null) { + var ancestorMember = cur.memberMap[name]; + if (ancestorMember != null) { member = ancestorMember; break; } + cur = cur.parentEnum; + } + } else if (VstMethod.?(member) && VstMethod.!(member).root != VstMethod.!(member)) { + // Subtype override: return root method for virtual dispatch. + member = VstMethod.!(member).root; + } + } if (member == null) return null; var memberType = inferMemberType(member); if (VstField.?(member)) { @@ -243,12 +258,15 @@ class MethodEnv { var memberType = Function.prependParamType(variantType, member.memberType); return expr.bind(null, VarBinding.ClassMethod(variantType, x, member.typeArgs), memberType); } - x: VstVariantTagMember => { + x: VstTagMember => { return newCompBinding(expr, V3Op.newVariantGetTag(variantType)); } - x: VstVariantNameMember => { + x: VstNameMember => { return newCompBinding(expr, ClassType.!(member.receiver).getNameOperator()); } + x: VstShortNameMember => { + return newCompBinding(expr, ClassType.!(member.receiver).getShortNameOperator()); + } x: VstCaseMember => { var memberType = member.memberType; if (x.decl.params != null) { @@ -274,12 +292,15 @@ class MethodEnv { x: VstMethod => { return expr.bind(receiver, VarBinding.ObjectMethod(variantType, x, member.typeArgs), member.memberType); } - x: VstVariantTagMember => { + x: VstTagMember => { return newApplyCompBinding(expr, receiver, V3Op.newVariantGetTag(variantType)); } - x: VstVariantNameMember => { + x: VstNameMember => { return newApplyCompBinding(expr, receiver, ClassType.!(member.receiver).getNameOperator()); } + x: VstShortNameMember => { + return newApplyCompBinding(expr, receiver, ClassType.!(member.receiver).getShortNameOperator()); + } } return VarBinding.None; } @@ -290,15 +311,20 @@ class MethodEnv { var name = expr.ident.name; var member = resolveClassMember(expr, variantType, name.image); if (member != null) { - if (VstVariantTagMember.?(member.member)) { + if (VstTagMember.?(member.member)) { var tagType = V3.getVariantTagType(variantType); return expr.bind(null, VarBinding.Const(Int.box(V3.getVariantTag(variantType)), tagType), tagType); } - if (VstVariantNameMember.?(member.member)) { + if (VstNameMember.?(member.member)) { var names = V3Class_TypeCon.!(V3.getRootType(variantType).typeCon).getNames(verifier.verifier.prog); var name = names.values[V3.getVariantTag(variantType)]; return expr.bind(null, VarBinding.Const(name, V3.stringType), V3.stringType); } + if (VstShortNameMember.?(member.member)) { + var names = V3Class_TypeCon.!(V3.getRootType(variantType).typeCon).getShortNames(verifier.verifier.prog); + var name = names.values[V3.getVariantTag(variantType)]; + return expr.bind(null, VarBinding.Const(name, V3.stringType), V3.stringType); + } } if (variantType.classDecl.params != null) return lookupVariantTypeMember(expr, variantType); return lookupVariantExprMember(expr, variantType, false); @@ -318,51 +344,90 @@ class MethodEnv { if (width < 1) width = 1; var countType = Int.getType(false, width); return expr.bind(null, VarBinding.Const(Int.box(count), countType), countType); +// } else if (Strings.equal(name, "shortName")) { +// return newCompBinding(expr, enumType.getShortNameOperator()); } var member = resolveEnumMember(expr, enumType, name); - if (member == null) return VarBinding.None; + if (member == null) { + // Check if the name is a subtype enum. + if (enumType.enumDecl.subtypes != null) { + var sub = enumType.enumDecl.subtypes[name]; + if (sub != null) { + var subType = EnumType.!(sub.typeCon.singleton); + return expr.bind(null, VarBinding.Type(subType), subType); + } + } + return VarBinding.None; + } match (member.member) { x: VstField => { var memberType = Function.newType(enumType, member.memberType); return expr.bind(null, VarBinding.Inst(enumType.enumGetParamOperator(x), N), memberType); } - x: VstVariantTagMember => { + x: VstTagMember => { var tagType = enumType.enumDecl.tagType; return newCompBinding(expr, V3Op.newVstSugar(VstOperator.Nop, [enumType, tagType], [enumType], tagType)); } - x: VstVariantNameMember => { + x: VstNameMember => { return newCompBinding(expr, enumType.getNameOperator()); } - x: VstEnumCase => { + x: VstShortNameMember => { + return newCompBinding(expr, enumType.getShortNameOperator()); + } + x: VstCaseMember => { return expr.bind(null, VarBinding.EnumConst(x), enumType); } + x: VstMethod => { + var methodReceiver = x.receiver.getDeclaredType(); + var memberType = Function.prependParamType(methodReceiver, member.memberType); + return expr.bind(null, VarBinding.ClassMethod(methodReceiver, x, member.typeArgs), memberType); + } } return VarBinding.None; } def lookupEnumExprMember(expr: VarExpr, enumType: EnumType, useThis: bool) -> VarBinding { // lookup (expr: enum).name var name = expr.ident.name; + if (Strings.equal(name.image, "shortName")) { + return newApplyCompBinding(expr, expr.expr, enumType.getShortNameOperator()); + } var member = resolveEnumMember(expr, enumType, name.image); if (member == null) return VarBinding.None; + var receiver = objExpr(useThis, expr.ident.name, expr); match (member.member) { x: VstField => { - return expr.bind(expr.expr, VarBinding.Apply(enumType.enumGetParamOperator(x), N), member.memberType); + return expr.bind(receiver, VarBinding.Apply(enumType.enumGetParamOperator(x), N), member.memberType); } - x: VstVariantTagMember => { + x: VstTagMember => { var tagType = enumType.enumDecl.tagType; - return newApplyCompBinding(expr, expr.expr, V3Op.newVstSugar(VstOperator.Nop, + return newApplyCompBinding(expr, receiver, V3Op.newVstSugar(VstOperator.Nop, [enumType, tagType], [enumType], tagType)); } - x: VstVariantNameMember => { - return newApplyCompBinding(expr, expr.expr, enumType.getNameOperator()); + x: VstNameMember => { + return newApplyCompBinding(expr, receiver, enumType.getNameOperator()); + } + x: VstShortNameMember => { + return newApplyCompBinding(expr, receiver, enumType.getShortNameOperator()); + } + x: VstMethod => { + var methodReceiver = x.receiver.getDeclaredType(); + return expr.bind(receiver, VarBinding.ObjectMethod(methodReceiver, x, member.typeArgs), member.memberType); } } return VarBinding.None; } - def lookupEnumConstMember(expr: VarExpr, enumType: EnumType, vc: VstEnumCase) -> VarBinding { + def lookupEnumConstMember(expr: VarExpr, enumType: EnumType, vc: VstCaseMember) -> VarBinding { // lookup (expr: enum.val).name var name = expr.ident.name; + if (Strings.equal(name.image, "shortName")) { + // Return the short name as a compile-time constant using the DFS global tag. + var shortNames = enumType.getShortNames(verifier.verifier.prog); + var idx = vc.enumTag; + if (idx < shortNames.values.length) { + return expr.bind(null, VarBinding.Const(shortNames.values[idx], V3.stringType), V3.stringType); + } + } var member = resolveEnumMember(expr, enumType, name.image); if (member == null) return lookupEnumExprMember(expr, enumType, false); match (member.member) { @@ -375,18 +440,72 @@ class MethodEnv { return expr.bind(null, VarBinding.Const(val, x.getType()), x.getType()); } } - x: VstVariantTagMember => { + x: VstTagMember => { var tagType = enumType.enumDecl.tagType; - return expr.bind(null, VarBinding.Const(Int.box(vc.tag), tagType), tagType); + // Use DFS global tag (enumTag) for runtime; fall back to parse-order tag if unassigned. + var tag = vc.enumTag; + return expr.bind(null, VarBinding.Const(Int.box(tag), tagType), tagType); } - x: VstVariantNameMember => { + x: VstNameMember => { var names = enumType.getNames(verifier.verifier.prog); - var name = names.values[vc.tag]; - return expr.bind(null, VarBinding.Const(name, V3.stringType), V3.stringType); + var idx = vc.enumTag; + var n = if(idx < names.values.length, names.values[idx], null); + return expr.bind(null, VarBinding.Const(n, V3.stringType), V3.stringType); + } + x: VstShortNameMember => { + var names = enumType.getShortNames(verifier.verifier.prog); + var idx = vc.enumTag; + var n = if(idx < names.values.length, names.values[idx], null); + return expr.bind(null, VarBinding.Const(n, V3.stringType), V3.stringType); + } + x: VstMethod => { + // Check for per-case override, then subtype-level override. + var overrideMethod = findEnumCaseOverride(vc, name.image); + if (overrideMethod == null) overrideMethod = findEnumSubtypeOverride(enumType.enumDecl, vc, name.image); + if (overrideMethod != null) { + // Use root enum type as receiver: override IrMethods are + // stored in the root IrClass (not the subtype's IrClass). + var rootDecl = enumType.enumDecl; + while (rootDecl.parentEnum != null) rootDecl = rootDecl.parentEnum; + var methodReceiver = rootDecl.getDeclaredType(); + var receiver = objExpr(false, expr.ident.name, expr); + return expr.bind(receiver, VarBinding.ObjectMethod(methodReceiver, overrideMethod, member.typeArgs), member.memberType); + } + return lookupEnumExprMember(expr, enumType, false); } } return lookupEnumExprMember(expr, enumType, false); } + def findEnumCaseOverride(vc: VstCaseMember, name: string) -> VstMethod { + if (vc.members == null) return null; + for (l = vc.members; l != null; l = l.tail) { + if (VstMethod.?(l.head) && Strings.equal(l.head.name(), name)) return VstMethod.!(l.head); + } + return null; + } + def findEnumSubtypeOverride(rootDecl: VstClass, vc: VstCaseMember, name: string) -> VstMethod { + // Walk from the case's declaring enum up to the root, looking for overrides. + var decl = findDeclaringEnum(rootDecl, vc); + while (decl != null) { + var member = decl.memberMap[name]; + if (member != null && VstMethod.?(member) && VstMethod.!(member).root != VstMethod.!(member)) { + return VstMethod.!(member); + } + decl = decl.parentEnum; + } + return null; + } + def findDeclaringEnum(rootDecl: VstClass, vc: VstCaseMember) -> VstClass { + // Find the enum that declares this case by searching the hierarchy. + for (c in rootDecl.cases) { + if (c == vc) return rootDecl; + } + for (l = rootDecl.subtypeOrder; l != null; l = l.tail) { + var result = findDeclaringEnum(l.head, vc); + if (result != null) return result; + } + return null; + } def lookupIntTypeMember(expr: VarExpr, etype: IntType) -> VarBinding { var name = expr.ident.name.image; var op: Operator, typeParams: List; @@ -514,12 +633,12 @@ class MethodEnv { var name = expr.ident.name.image; var tc = EnumSetType.!(etype); if (Strings.equal(name, "all")) { - return expr.bind(null, VarBinding.Const(tc.repType.max, etype), etype); + return expr.bind(null, VarBinding.Const(tc.allVal, etype), etype); } - var c = tc.enumDecl.memberMap[name]; - if (VstEnumCase.?(c)) { + var c = findEnumCaseInHierarchy(tc.enumDecl, name); + if (c != null) { // simple membership check - var op = V3Op.newVstSugar(VstOperator.EnumSetHas(VstEnumCase.!(c)), TypeUtil.NO_TYPES, [etype], Bool.TYPE); + var op = V3Op.newVstSugar(VstOperator.EnumSetHas(c), TypeUtil.NO_TYPES, [etype], Bool.TYPE); return expr.bind(null, VarBinding.Inst(op, N), op.sig.funcType()); } return VarBinding.None; @@ -527,14 +646,23 @@ class MethodEnv { def lookupEnumSetExprMember(expr: VarExpr, etype: Type) -> VarBinding { var name = expr.ident.name.image; var tc = EnumSetType.!(etype); - var c = tc.enumDecl.memberMap[name]; - if (VstEnumCase.?(c)) { + var c = findEnumCaseInHierarchy(tc.enumDecl, name); + if (c != null) { // simple membership check - var op = V3Op.newVstSugar(VstOperator.EnumSetHas(VstEnumCase.!(c)), TypeUtil.NO_TYPES, [etype], Bool.TYPE); + var op = V3Op.newVstSugar(VstOperator.EnumSetHas(c), TypeUtil.NO_TYPES, [etype], Bool.TYPE); return expr.bind(expr.expr, VarBinding.Apply(op, N), Bool.TYPE); } return VarBinding.None; } + def findEnumCaseInHierarchy(decl: VstClass, name: string) -> VstCaseMember { + var m = decl.memberMap[name]; + if (VstCaseMember.?(m)) return VstCaseMember.!(m); + for (l = decl.subtypeOrder; l != null; l = l.tail) { + var c = findEnumCaseInHierarchy(l.head, name); + if (c != null) return c; + } + return null; + } def lookupRefTypeMember(expr: VarExpr, refType: RefType) -> VarBinding { var name = expr.ident.name.image; if (Strings.equal("at", name)) { diff --git a/aeneas/src/vst/Parser.v3 b/aeneas/src/vst/Parser.v3 index 1a3687fcd..83fe70be5 100644 --- a/aeneas/src/vst/Parser.v3 +++ b/aeneas/src/vst/Parser.v3 @@ -211,6 +211,9 @@ component Parser { var decl = desugar.synthesizeTopLevelClass(parseVariantCases(p, desugar)); decl.repHints = repHints; decl.qualifiers = qualifiers; + for (c in decl.cases) { + if (Strings.equal(c.name(), "_")) { decl.hasDefault = true; break; } + } decl.qualifierTypeArgs = qualTypeArgs; file.classes.put(decl); // lift case classes to top-level of file @@ -444,35 +447,139 @@ component Parser { return true; } def parseEnum(p: ParserState, file: VstFile, isPrivate: bool) -> bool { + // Parse an optional dotted qualifier prefix (e.g. "E1." in "enum E1.More") then + // the enum name as a plain identifier (no type params, unlike variants). + var qualifiers: List; + var d = p.input; + while (Char.isIdentStart(p.curByte)) { + var q = p.curPos + 1; + while (q < d.length && Char.isIdentMiddle(d[q])) q++; + if (q < d.length && d[q] == '.' && q + 1 < d.length && Char.isIdentStart(d[q + 1])) { + var qid = parseIdentVoid(p); + p.advance1(); + qualifiers = List.new(qid.name, qualifiers); + } else { + break; + } + } + qualifiers = Lists.reverse(qualifiers); var id = parseIdentVoid(p); - var params = parseOptionalParams(p, parseEnumParam); - // Synthesize "tag" and "name" members - var tagToken = Token.new(p.fileName, "tag", p.curLine, p.curCol); - var nameToken = Token.new(p.fileName, "name", p.curLine, p.curCol); - var list: List; - list = List.new(VstVariantTagMember.new(tagToken), list); - list = List.new(VstVariantNameMember.new(nameToken), list); + var superParams = false; + var params: VstList; + if (p.curByte == '(') { + var d = p.input; + var q = p.curPos + 1; + while (q < d.length && (d[q] == ' ' || d[q] == '\t')) q++; + if (q + 5 <= d.length && d[q] == 's' && d[q+1] == 'u' && d[q+2] == 'p' + && d[q+3] == 'e' && d[q+4] == 'r' + && (q + 5 >= d.length || !Char.isIdentMiddle(d[q+5]))) { + var r = q + 5; + while (r < d.length && (d[r] == ' ' || d[r] == '\t')) r++; + if (r < d.length && d[r] == ')') { + // (super) only + p.advance1(); + optKeyword(p, "super"); + p.req1(')'); + superParams = true; + } else if (r < d.length && d[r] == ',') { + // (super, extra_params...) + p.advance1(); // consume '(' + optKeyword(p, "super"); // consume 'super' + p.advance1(); // consume ',' + var paramList: List; + paramList = List.new(parseEnumParam(p), paramList); + while (p.curByte == ',') { + p.advance1(); + paramList = List.new(parseEnumParam(p), paramList); + } + p.req1(')'); + params = VstList.new(id.name.range(), Lists.reverse(paramList)); + superParams = true; + } + } + } + if (!superParams) params = parseOptionalParams(p, parseEnumParam); + var desugar = EnumDesugaring.new(isPrivate, id, params); + var list = desugar.synthesizeMembers(p.fileName, p.curLine, p.curCol); // Parse case members - var cases = Vector.new(); p.req1('{'); while (true) { if (p.curByte == '}') { p.advance1(); break; } - var c = parseEnumCase(p, cases.length); + if (p.curByte == ';') break; // methods section follows + var c = parseEnumCase(p, desugar.cases.length); if (c == null) { p.opt1('}'); break; } - cases.put(c); + desugar.addCase(c); list = List.new(c, list); + if (desugar.hasDefault) { + if (p.curByte == ';') break; // methods after _ + p.req1('}'); + break; + } } - var decl = VstEnum.new(isPrivate, id.name, params, cases.extract(), list); + // Parse optional enum-level methods after ';' + if (p.curByte == ';') { + p.advance1(); + while (true) { + if (p.curByte == '}') { p.advance1(); break; } + var nlist = parseEnumMethod(p, list); + if (nlist == list) { p.opt1('}'); break; } + list = nlist; + } + } + var decl = desugar.synthesizeTopLevelClass(list); + decl.superParams = superParams; + decl.qualifiers = qualifiers; file.enums.put(decl); return true; } - def parseEnumCase(p: ParserState, tag: int) -> VstEnumCase { + def parseEnumCase(p: ParserState, tag: int) -> VstCaseMember { + // Handle "_" default case specially (not an identifier start in Virgil). + if (p.curByte == '_' && !Char.isIdentMiddle(p.peek(1))) { + var ut = p.token(1); + var c = VstCaseMember.new(tag, ut, null, null); + if (p.curByte == '{') c.members = parseEnumCaseMembers(p); + p.opt1(','); + return c; + } var id = parseIdentVoid(p); var args = if(p.curByte == '(', parseList(0, p, '(', COMMA, ')', parseExpr)); + if (id.name.image.length > 0) { + var c = VstCaseMember.new(tag, id.name, null, args); + if (p.curByte == '{') c.members = parseEnumCaseMembers(p); + p.opt1(','); + return c; + } p.opt1(','); - if (id.name.image.length > 0) return VstEnumCase.new(tag, id.name, args); return null; } + def parseEnumCaseMembers(p: ParserState) -> List { + p.req1('{'); + var list: List; + while (true) { + if (p.curByte == '}') { p.advance1(); break; } + var isPrivate = optKeyword(p, "private") != null; + if (optKeyword(p, "def") != null) { + var parser = if(isPrivate, PRIVATE_DEF, PUBLIC_DEF); + var nlist = parser.parseDef(p, list, null); + if (nlist == list) { p.opt1('}'); break; } + list = nlist; + } else { + p.error("only 'def' allowed in enum case body"); + p.opt1('}'); + break; + } + } + return Lists.reverse(list); + } + def parseEnumMethod(p: ParserState, prev: List) -> List { + var isPrivate = optKeyword(p, "private") != null; + if (optKeyword(p, "def") != null) { + var parser = if(isPrivate, PRIVATE_DEF, PUBLIC_DEF); + return parser.parseDef(p, prev, null); + } + p.error("expected 'def' in enum method section"); + return prev; + } def parseLayout(p: ParserState, file: VstFile) -> bool { var id = parseIdentVoid(p).name; var repHints = parseRepHints(p); diff --git a/aeneas/src/vst/Verifier.v3 b/aeneas/src/vst/Verifier.v3 index 3e3a1bd12..6064a1d83 100644 --- a/aeneas/src/vst/Verifier.v3 +++ b/aeneas/src/vst/Verifier.v3 @@ -25,16 +25,20 @@ class Verifier(compiler: Compiler, prog: Program) { var vst = prog.vst; for (f in vst.files) buildFile(f); forAll(vst.classes, resolveSuperClass); + forAll(vst.enums, verifyEnum); + forAll(vst.enums, verifyEnumQualifiers); + forAll(vst.enums, fixEnumMethodRoots); + forAll(vst.enums, assignEnumTagsIfRoot); + forAll(vst.enums, finalizeEnumType); forAll(vst.components, verifyComponent); forAll(vst.classes, verifyClass); forAll(vst.classes, assignVariantTagsIfRoot); - forAll(vst.enums, verifyEnum); forAll(vst.layouts, verifyLayout); forAll(vst.exports, verifyExport); forAll(vst.packings, verifyPacking); forAll(vst.components, typeCheckVstCompound); forAll(vst.classes, typeCheckVstCompound); - forAll(vst.enums, typeCheckVstCompound); + forAll(vst.enums, typeCheckVstCompound); forAll(vst.layouts, typeCheckVstCompound); // Method override checks are deferred until after body inference. @@ -85,6 +89,19 @@ class Verifier(compiler: Compiler, prog: Program) { prog.vst.enums.putv(file.enums); for (i < file.enums.length) { var enumDecl = file.enums[i]; + enumDecl.tagType = V3.getTagType(enumDecl.cases.length); + var tc = TypeCon.new(enumDecl.token.image, Kind.ENUM, 0, prog.typeCache); + var enumType = EnumType.new(tc.uid, enumDecl, tc); + tc.singleton = enumType; + enumDecl.typeCon = tc; + if (enumDecl.cases.length <= Int.MAX_WIDTH) { + var setName = Strings.builderOf(enumDecl.token.image).puts(".set").toString(); + var stc = TypeCon.new(setName, Kind.ENUM_SET, 0, prog.typeCache); + var count = if(enumDecl.cases.length == 0, 1, enumDecl.cases.length); + var repType = Int.getType(false, count); + var enumSetType = EnumSetType.new(stc.uid, enumDecl, repType, stc); + enumType.setType = stc.singleton = enumSetType; + } var cv = makeVerifier(enumDecl, enumDecl.getDeclaredType(), file); cv.bindTypeCon(); cv.buildMembers(); @@ -122,12 +139,34 @@ class Verifier(compiler: Compiler, prog: Program) { var constructor = ic.methods[0]; if (constructor != null && VstComponent.?(decl)) addInit(constructor); } - def addEnumVals(decl: VstEnum) { + def addEnumVals(decl: VstClass) { + if (decl.qualifiers != null) { + var rootEnum = findRootEnum(decl); + var needsInit = rootEnum != null && rootEnum.params != null; + // Create enumVals for subtype-only (extra) params + if (decl.extraParams != null) { + var arraySize = if(rootEnum.tagHi >= 0, rootEnum.tagHi + 1, decl.cases.length); + for (l = decl.extraParams.list; l != null; l = l.tail) { + var field = VstField.!(l.head.member); + var arrayType = V3Array.newType(field.getType()); + field.enumVals = prog.newRecord(arrayType, arraySize); + } + needsInit = true; + } + if (needsInit) { + var ic = prog.ir.makeIrClass(decl.getDeclaredType()); + var constructor = ic.methods[0]; + if (constructor != null) addInit(constructor); + } + return; + } if (decl.params == null) return; + // Root enum: size param arrays to cover all DFS-assigned tags (including subtypes). + var arraySize = if(decl.tagHi >= 0, decl.tagHi + 1, decl.cases.length); for (l = decl.params.list; l != null; l = l.tail) { var field = VstField.!(l.head.member); var arrayType = V3Array.newType(field.getType()); - field.enumVals = prog.newRecord(arrayType, decl.cases.length); + field.enumVals = prog.newRecord(arrayType, arraySize); } var ic = prog.ir.makeIrClass(decl.getDeclaredType()); var constructor = ic.methods[0]; @@ -226,7 +265,7 @@ class Verifier(compiler: Compiler, prog: Program) { q = q.tail; // Walk any remaining qualifiers (for names like A.B.C). while (q != null) { - if (!variantHasDefaultCase(parentClass)) { + if (!parentClass.hasDefault) { cv.errAtToken(q.head).set("VariantDeclError", Strings.format1("variant \"%s\" has no 'case _'", parentClass.name())); return; @@ -248,7 +287,7 @@ class Verifier(compiler: Compiler, prog: Program) { q = q.tail; } // parentClass is the immediate parent of decl. - if (!variantHasDefaultCase(parentClass)) { + if (!parentClass.hasDefault) { cv.errAtDecl(decl).set("VariantDeclError", Strings.format1("variant \"%s\" has no 'case _'", parentClass.name())); return; @@ -300,7 +339,7 @@ class Verifier(compiler: Compiler, prog: Program) { parentClassType.superType = parentClass.verifier.superType; // If the parent has a case _, route inheritance through it so its methods are // visible to this subtype (and may override parent methods). - var defaultCaseDecl = findDefaultCase(parentClass); + var defaultCaseDecl = parentClass.findDefaultCase(); if (defaultCaseDecl != null) verifyClass(defaultCaseDecl); var effectiveSuperDecl = if(defaultCaseDecl != null, defaultCaseDecl, parentClass); var effectiveSuperType = ClassType.!(if(typeParamTypes != null, @@ -342,20 +381,6 @@ class Verifier(compiler: Compiler, prog: Program) { } } } - // Returns true if the variant class has a "case _" default case. - def variantHasDefaultCase(decl: VstClass) -> bool { - for (c in decl.cases) { - if (Strings.equal(c.name(), "_")) return true; - } - return false; - } - // Returns the synthetic VstClass for the "case _" of a variant, or null if none. - def findDefaultCase(decl: VstClass) -> VstClass { - for (c in decl.cases) { - if (Strings.equal(c.name(), "_")) return c.decl; - } - return null; - } // Returns true if this variant has at least one named "case id" (not "case _"). def variantHasNamedCases(decl: VstClass) -> bool { if (decl.cases == null) return false; @@ -374,7 +399,7 @@ class Verifier(compiler: Compiler, prog: Program) { sd.isSynthetic = true; sd.fullName = Strings.format1("%s.$default", parent.fullName); sd.variantTag = tag; - sd.variantTagHi = tag; + sd.tagHi = tag; parent.syntheticDefault = sd; } // Entry point for DFS tag assignment: only process root variants (no qualifiers). @@ -396,13 +421,13 @@ class Verifier(compiler: Compiler, prog: Program) { for (l = decl.subtypeOrder; l != null; l = l.tail) { nextTag = assignTagsDFS(l.head, nextTag); } - var defaultCase = findDefaultCase(decl); + var defaultCase = decl.findDefaultCase(); if (defaultCase != null) { - defaultCase.variantTagHi = nextTag - 1; + defaultCase.tagHi = nextTag - 1; } } } - decl.variantTagHi = nextTag - 1; + decl.tagHi = nextTag - 1; // Recompute the tag type to fit all newly assigned tags. var width = 1, exp = 2; while (exp < nextTag) { exp = exp << 1; width++; } @@ -418,7 +443,7 @@ class Verifier(compiler: Compiler, prog: Program) { var hasChildren = (decl.cases != null && decl.cases.length > 0) || decl.subtypeOrder != null; if (!hasChildren) { // Leaf: consume exactly one tag. - decl.variantTagHi = nextTag; + decl.tagHi = nextTag; return nextTag + 1; } // Non-leaf: distribute range to children in declaration order. @@ -432,12 +457,12 @@ class Verifier(compiler: Compiler, prog: Program) { nextTag = assignTagsDFS(l.head, nextTag); } // Extend "case _" tagHi to cover the subtypes' range. - var defaultCase = findDefaultCase(decl); + var defaultCase = decl.findDefaultCase(); if (defaultCase != null) { - defaultCase.variantTagHi = nextTag - 1; + defaultCase.tagHi = nextTag - 1; } } - decl.variantTagHi = nextTag - 1; + decl.tagHi = nextTag - 1; return nextTag; } // Propagate the root's tagType down to all cases and subtype variants. @@ -451,10 +476,272 @@ class Verifier(compiler: Compiler, prog: Program) { propagateTagType(l.head, tagType); } } - def verifyEnum(decl: VstEnum) { + def verifyEnum(decl: VstClass) { var cv = decl.verifier; cv.verify(); } + def fixEnumMethodRoots(decl: VstClass) { + // After verifyEnumQualifiers sets parentEnum, fix method root pointers + // for subtype enums. During verifyEnum, parentEnum isn't set yet, so + // checkMethod treats subtype override methods as roots (root = self). + if (decl.parentEnum == null) return; + var cv = decl.verifier; + for (l = decl.members; l != null; l = l.tail) { + if (!VstMethod.?(l.head)) continue; + var m = VstMethod.!(l.head); + if (m.root != m) continue; // already correctly set + var parentMethod = cv.findEnumMethodInParents(VstClass.!(cv.compound), m.name()); + if (parentMethod != null) { + m.root = parentMethod.root; + m.index = parentMethod.index; + } + } + // Also fix per-case method overrides. + for (c in decl.cases) { + if (c.members == null) continue; + for (l = c.members; l != null; l = l.tail) { + if (!VstMethod.?(l.head)) continue; + var m = VstMethod.!(l.head); + // Case overrides should point to the ultimate root. + if (m.root != null && m.root.root != null && m.root != m.root.root) { + m.root = m.root.root; + } + } + } + } + def verifyEnumQualifiers(decl: VstClass) { + var q = decl.qualifiers; + if (q == null) return; // no qualifiers → root enum, nothing to resolve + var cv = decl.verifier; + // Resolve first qualifier: must be a top-level (root) enum. + var firstTok = q.head; + var tc = prog.typeEnv.lookup(firstTok.image); + if (tc == null || tc.kind != Kind.ENUM) { + cv.errAtDecl(decl).set("EnumDeclError", + Strings.format1("qualifier \"%s\" does not name an enum type", firstTok.image)); + return; + } + var parentDecl = EnumType.!(tc.create0()).enumDecl; + if (parentDecl.qualifiers != null) { + cv.errAtDecl(decl).set("EnumDeclError", + Strings.format1("\"%s\" must be a top-level enum type", firstTok.image)); + return; + } + q = q.tail; + // Walk any remaining qualifiers (for names like E1.More.Sub). + while (q != null) { + if (!parentDecl.hasDefault) { + cv.errAtToken(q.head).set("EnumDeclError", + Strings.format1("enum \"%s\" has no '_ case' to allow subtypes", parentDecl.name())); + return; + } + var tok = q.head; + if (parentDecl.subtypes == null || parentDecl.subtypes[tok.image] == null) { + cv.errAtToken(tok).set("EnumDeclError", + Strings.format1("qualifier \"%s\" is not a declared subtype enum", tok.image)); + return; + } + parentDecl = parentDecl.subtypes[tok.image]; + q = q.tail; + } + // parentDecl is the immediate parent of decl. + if (!parentDecl.hasDefault) { + cv.errAtDecl(decl).set("EnumDeclError", + Strings.format1("enum \"%s\" has no '_ case' to allow subtypes", parentDecl.name())); + return; + } + var declName = decl.name(); + // Name must not clash with a named case of the parent (excluding _). + for (c in parentDecl.cases) { + if (!Strings.equal(c.name(), "_") && Strings.equal(c.name(), declName)) { + cv.errAtDecl(decl).set("EnumDeclError", + Strings.format2("subtype name \"%s\" conflicts with case of \"%s\"", + declName, parentDecl.name())); + return; + } + } + // Name must be unique among direct subtypes of the parent. + if (parentDecl.subtypes == null) parentDecl.subtypes = Strings.newMap(); + if (parentDecl.subtypes[declName] != null) { + cv.errAtDecl(decl).set("EnumDeclError", + Strings.format2("subtype \"%s\" of \"%s\" declared more than once", + declName, parentDecl.name())); + } else { + parentDecl.subtypes[declName] = decl; + parentDecl.subtypeOrder = List.new(decl, parentDecl.subtypeOrder); + } + decl.parentEnum = parentDecl; + // Validate subtype params against parent's effective params. + var rootDecl = findRootEnum(decl); + // Build parent's effective params: root params + all ancestor extras + var effectiveParams = buildEffectiveParams(rootDecl, parentDecl); + if (decl.superParams && effectiveParams == null) { + cv.errAtDecl(decl).set("EnumDeclError", + "'super' used but parent enum has no parameters"); + return; + } + if (decl.superParams && decl.params != null) { + // Form: (super, extra...) — decl.params has only new params + decl.extraParams = decl.params; + } else if (decl.params != null && effectiveParams != null) { + // Form 1 or Form 1+add: explicit redecl, possibly with extras + var dp = decl.params.list; + var ep = effectiveParams; + var i = 0; + while (dp != null && ep != null) { + if (!Strings.equal(dp.head.token.image, ep.head.token.image)) { + cv.errAtDecl(decl).set("EnumDeclError", + Strings.format3("parameter %d name \"%s\" does not match parent's \"%s\"", + i, dp.head.token.image, ep.head.token.image)); + return; + } + if (dp.head.vtype != ep.head.vtype) { + cv.errAtDecl(decl).set("EnumDeclError", + Strings.format1("parameter \"%s\" type does not match parent's type", + dp.head.token.image)); + return; + } + dp = dp.tail; + ep = ep.tail; + i++; + } + if (ep != null) { + cv.errAtDecl(decl).set("EnumDeclError", + "subtype has fewer parameters than parent's effective parameters"); + return; + } + if (dp != null) { + // Extra params beyond parent's effective + decl.extraParams = VstList.new(decl.params.src, dp); + } + } else if (decl.params != null && effectiveParams == null) { + cv.errAtDecl(decl).set("EnumDeclError", + "subtype declares parameters but parent enum has none"); + return; + } + // Mark constructor nontrivial when parent has effective params or subtype has extras + if (decl.params == null && (effectiveParams != null || decl.extraParams != null)) { + if (decl.constructor != null) decl.constructor.markNontrivial(); + } + } + def findRootEnum(decl: VstClass) -> VstClass { + var root = decl; + if (root.qualifiers != null) { + var tc = prog.typeEnv.lookup(root.qualifiers.head.image); + if (tc != null && tc.kind == Kind.ENUM) { + root = EnumType.!(tc.create0()).enumDecl; + } + } + return root; + } + // Build the effective param list for an enum: root params + all ancestor extras + // in root-to-target order. Returns null if no effective params. + def buildEffectiveParams(rootDecl: VstClass, target: VstClass) -> List { + var result: List; + if (rootDecl.params != null) { + for (l = rootDecl.params.list; l != null; l = l.tail) { + result = List.new(l.head, result); + } + } + // Walk from target up to root, collecting extras (builds in reverse order) + var extras: List>; + var cur = target; + while (cur != null && cur != rootDecl) { + if (cur.extraParams != null) extras = List.new(cur.extraParams.list, extras); + cur = cur.parentEnum; + } + // extras is now in root-to-target order (reversed during walk) + for (el = extras; el != null; el = el.tail) { + for (l = el.head; l != null; l = l.tail) { + result = List.new(l.head, result); + } + } + return Lists.reverse(result); + } + def assignEnumTagsIfRoot(decl: VstClass) { + if (decl.qualifiers != null) return; // skip subtype enums + var nextTag = 0; + nextTag = assignEnumTagsDFS(decl, nextTag); + // Recompute the tag type to fit all newly assigned tags. + var total = nextTag; + var width = 1, exp = 2; + while (exp < total) { exp = exp << 1; width++; } + var tagType = Int.getType(false, byte.!(width)); + propagateEnumTagType(decl, tagType); + } + def assignEnumTagsDFS(decl: VstClass, nextTag: int) -> int { + decl.tagLo = nextTag; + // Assign named cases (excluding _) in declaration order. + for (c in decl.cases) { + if (Strings.equal(c.name(), "_")) continue; + c.enumTag = nextTag++; + } + var defaultLo = nextTag; + // Recurse into subtypes in declaration order (subtypeOrder is reverse-decl, so reverse it). + var subtypes = Lists.reverse(decl.subtypeOrder); + for (l = subtypes; l != null; l = l.tail) { + nextTag = assignEnumTagsDFS(l.head, nextTag); + } + // Assign _ case's enumTag to the start of subtype range (inclusive low bound). + for (c in decl.cases) { + if (Strings.equal(c.name(), "_")) c.enumTag = defaultLo; + } + decl.tagHi = nextTag - 1; + return nextTag; + } + def propagateEnumTagType(decl: VstClass, tagType: IntType) { + decl.tagType = tagType; + for (l = decl.subtypeOrder; l != null; l = l.tail) { + propagateEnumTagType(l.head, tagType); + } + } + def finalizeEnumType(decl: VstClass) { + if (decl.qualifiers != null) return; // only process from root + var enumType = EnumType.!(decl.typeCon.singleton); + var isHierarchy = decl.hasDefault || decl.subtypeOrder != null; + if (!isHierarchy) { + // Non-hierarchy enum: constructor-created set type is correct. + if (enumType.setType != null) enumType.setType.allVal = enumType.setType.repType.max; + return; + } + // Hierarchy enum: recreate set type with DFS-aware sizing. + var totalTags = if(decl.tagHi >= 0, decl.tagHi + 1, decl.cases.length); + if (totalTags > Int.MAX_WIDTH) { + enumType.setType = null; // too large for set + return; + } + if (totalTags == 0) totalTags = 1; + var repType = Int.getType(false, totalTags); + var setName = Strings.builderOf(decl.token.image).puts(".set").toString(); + var tc = TypeCon.new(setName, Kind.ENUM_SET, 0, TypeUtil.globalCache); + var enumSetType = EnumSetType.new(tc.uid, decl, repType, tc); + tc.singleton = enumSetType; + enumType.setType = enumSetType; + // Compute allVal: union of (1 << c.enumTag) for all named cases. + enumSetType.allVal = computeEnumSetAll(decl, repType); + // Propagate shared set type to subtypes. + propagateEnumSetType(decl, enumSetType); + } + def computeEnumSetAll(decl: VstClass, repType: IntType) -> Val { + var bits: long = 0; + for (c in decl.cases) { + if (c.enumTag >= 0 && !Strings.equal(c.name(), "_")) { + bits = bits | (1L << u6.!(c.enumTag)); + } + } + for (l = decl.subtypeOrder; l != null; l = l.tail) { + var sub = computeEnumSetAll(l.head, repType); + if (repType.width <= 32) bits = bits | long.!(Int.unbox(sub)); + else bits = bits | Long.unboxSU(sub, repType.signed); + } + return if(repType.width <= 32, Int.box(int.!(bits)), Long.box(bits)); + } + def propagateEnumSetType(decl: VstClass, setType: EnumSetType) { + for (l = decl.subtypeOrder; l != null; l = l.tail) { + EnumType.!(l.head.typeCon.singleton).setType = setType; + propagateEnumSetType(l.head, setType); + } + } def verifyLayout(decl: VstLayout) { var cv = decl.verifier; var size = tryUnboxPositiveInt(decl.size, -1); @@ -555,9 +842,23 @@ class Verifier(compiler: Compiler, prog: Program) { tc.enterMethod(x, env); // TODO: ugly side-effect of creating thisParam if (!EmptyStmt.?(x.func.body)) tc.typeCheckBody(x.func.body); } - x: VstEnumCase => { + x: VstCaseMember => { tc.enterCompound(decl, false, env); - typeCheckEnumCase(VstEnum.!(decl), x, tc); + typeCheckEnumCase(VstClass.!(decl), x, tc); + } + } + } + // Type-check per-case method override bodies. + if (VstClass.?(decl) && VstClass.!(decl).isEnum()) { + for (c in VstClass.!(decl).cases) { + if (c.members == null) continue; + for (l = c.members; l != null; l = l.tail) { + if (VstMethod.?(l.head)) { + var m = VstMethod.!(l.head); + if (m.root == m) continue; // error in verification; skip body + tc.enterMethod(m, env); + if (!EmptyStmt.?(m.func.body)) tc.typeCheckBody(m.func.body); + } } } } @@ -565,7 +866,7 @@ class Verifier(compiler: Compiler, prog: Program) { def typeCheckNew(decl: VstCompound, cdecl: VstNew, tc: TypeChecker) { var cv = decl.verifier, thisParam = cv.newThisParam(), pl = cdecl.func.params; var initEnv: MethodEnv; - if (VstClass.?(decl) || VstEnum.?(decl)) { + if (VstClass.?(decl)) { // track field initialization state for class constructors initEnv = tc.enterCompound(decl, true, env); } else { @@ -615,15 +916,26 @@ class Verifier(compiler: Compiler, prog: Program) { initEnv.initOrder = 0; // all fields can be used in the constructor tc.typeCheckBody(cdecl.func.body); } - def typeCheckEnumCase(decl: VstEnum, c: VstEnumCase, tc: TypeChecker) { + def typeCheckEnumCase(decl: VstClass, c: VstCaseMember, tc: TypeChecker) { + if (Strings.equal(c.name(), "_")) return; // _ case has no args var cv = decl.verifier; + // Build effective param types: root params + all ancestor extras + own extras var paramTypes: List; - if (decl.params != null) { - // extract parameter types. + var paramCount = 0; + if (decl.qualifiers != null) { + var root = findRootEnum(decl); + var effective = buildEffectiveParams(root, decl); + for (l = effective; l != null; l = l.tail) { + paramTypes = List.new(l.head.vtype, paramTypes); + paramCount++; + } + paramTypes = Lists.reverse(paramTypes); + } else if (decl.params != null) { for (params = decl.params.list; params != null; params = params.tail) { paramTypes = List.new(params.head.vtype, paramTypes); } paramTypes = Lists.reverse(paramTypes); + paramCount = decl.params.length(); } if (c.args != null) { // typecheck arguments to enum. @@ -633,11 +945,11 @@ class Verifier(compiler: Compiler, prog: Program) { if (paramTypes != null) { tc.typeCheckExpr(args.head, paramTypes.head, what); } else { - cv.errAtExpr(args.head).ArityMismatch(what, decl.params.length(), c.args.length()); + cv.errAtExpr(args.head).ArityMismatch(what, paramCount, c.args.length()); return; } } - if (paramTypes != null) cv.errAtDecl(c).ArityMismatch(what, decl.params.length(), c.args.length()); + if (paramTypes != null) cv.errAtDecl(c).ArityMismatch(what, paramCount, c.args.length()); } else { // expected parameters, but none were provided. if (paramTypes != null) cv.errAtDecl(c).CaseDeclError("expected case arguments, but none provided"); @@ -719,7 +1031,7 @@ class VstCompoundVerifier { ERROR = verifier.ERROR; if (VstClass.?(compound)) { classDecl = VstClass.!(compound); - classType = ClassType.!(thisType); + if (ClassType.?(thisType)) classType = ClassType.!(thisType); } else if (VstComponent.?(compound)) { isImport = VstComponent.!(compound).importName != null; } @@ -807,8 +1119,8 @@ class VstCompoundVerifier { typeEnv.add(decl.typeCon); } def verify() { - if (VstEnum.?(compound)) { - if (VstEnum.!(compound).cases.length == 0) { + if (classDecl != null && classDecl.isEnum()) { + if (classDecl.cases.length == 0) { errAtDecl(compound).set("EnumDeclError", "enum requires at least 1 case"); } } @@ -820,6 +1132,12 @@ class VstCompoundVerifier { x: VstLayoutField => checkLayoutField(x); } } + // Verify per-case method overrides for enums. + if (classDecl != null && classDecl.isEnum()) { + for (c in classDecl.cases) { + if (c.members != null) checkEnumCaseMembers(c); + } + } if (compound.constructor != null) { compound.constructor.memberinits = Lists.reverse(memberinits); } @@ -939,6 +1257,16 @@ class VstCompoundVerifier { } var binding = lookupSuperMember(decl.name()); if (binding == null) { + // Check parent enum chain for subtype enum method overrides. + if (VstClass.?(compound) && VstClass.!(compound).parentEnum != null) { + var parentMethod = findEnumMethodInParents(VstClass.!(compound), decl.name()); + if (parentMethod != null) { + decl.root = parentMethod.root; + decl.index = parentMethod.index; + verifier.methodOverrideChecks = List.new((this, decl), verifier.methodOverrideChecks); + return; + } + } decl.root = decl; decl.index = compound.numMethods++; return; @@ -1011,6 +1339,13 @@ class VstCompoundVerifier { } def checkMethodOverride(decl: VstMethod) { var binding = lookupSuperMember(decl.name()); // re-resolve member after return type inference + if (binding == null) { + // Enum case override: compare directly against root method. + if (decl.root != null && decl.root != decl) { + checkEnumCaseMethodOverride(decl); + } + return; + } var superDecl = VstMethod.!(binding.member); var superMethodType = binding.memberType; var methodType = decl.getType(); @@ -1039,6 +1374,62 @@ class VstCompoundVerifier { errAtDecl(decl).InheritanceError2("method signature %s cannot override %s", methodType, superMethodType); } } + def checkEnumCaseMembers(c: VstCaseMember) { + for (l = c.members; l != null; l = l.tail) { + match (l.head) { + x: VstMethod => checkEnumCaseMethod(x, c); + _ => errAtDecl(l.head).set("EnumDeclError", "only 'def' allowed in enum case body"); + } + } + } + def checkEnumCaseMethod(decl: VstMethod, ecase: VstCaseMember) { + decl.receiver = compound; + // Look up the root method in the enum's member map or parent chain. + var rootMember = compound.memberMap[decl.name()]; + if ((rootMember == null || !VstMethod.?(rootMember)) && compound.isEnum()) { + var pm = findEnumMethodInParents(VstClass.!(compound), decl.name()); + if (pm != null) rootMember = pm; + } + if (rootMember == null || !VstMethod.?(rootMember)) { + errAtDecl(decl).set("EnumDeclError", + Strings.format1("case method \"%s\" does not override an enum-level method", decl.name())); + decl.root = decl; + return; + } + var rootMethod = VstMethod.!(rootMember); + decl.root = rootMethod.root; + decl.index = rootMethod.index; + // Check parameters. + typeEnv = compound.typeEnv; + if (decl.func.params.list != null) { + var map = Strings.newMap(); + Lists.apply(decl.func.params.list, checkMethodParam(_, map)); + } + match (decl.func.rettype) { + Explicit(tref) => resolveType(tref, typeEnv); + Implicit(expr) => ; + _ => ; + } + // Defer override signature check until after body inference. + verifier.methodOverrideChecks = List.new((this, decl), verifier.methodOverrideChecks); + } + def checkEnumCaseMethodOverride(decl: VstMethod) { + var rootMethod = decl.root; + var superMethodType = rootMethod.getType(); + var methodType = decl.getType(); + if (superMethodType != null && methodType != null && !TypeSystem.isSubtype(methodType, superMethodType)) { + errAtDecl(decl).InheritanceError2("method signature %s cannot override %s", methodType, superMethodType); + } + } + def findEnumMethodInParents(decl: VstClass, name: string) -> VstMethod { + var cur = decl.parentEnum; + while (cur != null) { + var member = cur.memberMap[name]; + if (member != null && VstMethod.?(member)) return VstMethod.!(member); + cur = cur.parentEnum; + } + return null; + } def checkParam(param: ParamDecl, map: Map) { if (map[param.name()] != null) errAtDecl(param).ParamDeclError(Strings.format1("parameter \"%s\" redefined", param.name())); map[param.name()] = param; @@ -1115,7 +1506,7 @@ class VstCompoundVerifier { } } } else if (EnumType.?(leftType)) { - // Enum.set resolution. + // Enum.set or Enum.SubtypeEnum resolution. var enumType = EnumType.!(leftType); if (Strings.equal(x.name.image, "set")) { if (x.nested != null) { @@ -1124,6 +1515,34 @@ class VstCompoundVerifier { } return getEnumSetType(x, enumType); } + // Check if name is a subtype enum. + if (enumType.enumDecl.subtypes != null) { + var subDecl = enumType.enumDecl.subtypes[x.name.image]; + if (subDecl != null) { + if (x.nested != null) { + errAtRange(x.name.range()).TypeParamArityError("enum subtype", + x.name.image, 0, x.nested.length()); + } + return x.binding = subDecl.typeCon.create0(); + } + } + // Fall back: subtypes map may not be populated yet (early type resolution). + var subTc = verifier.prog.typeEnv.lookup(x.name.image); + if (subTc != null && subTc.kind == Kind.ENUM) { + var subDecl = EnumType.!(subTc.create0()).enumDecl; + if (subDecl.qualifiers != null) { + // Accept if qualifiers start with the parent enum's name. + var q = subDecl.qualifiers; + while (q.tail != null) q = q.tail; + if (Strings.equal(q.head.image, enumType.enumDecl.name())) { + if (x.nested != null) { + errAtRange(x.name.range()).TypeParamArityError("enum subtype", + x.name.image, 0, x.nested.length()); + } + return x.binding = subDecl.typeCon.create0(); + } + } + } } if (x.left != null) { errAtRange(x.name.range()).set("UnresolvedType", Strings.format2("type \"%q\" has no member type \"%s\"", x.left.render, x.name.image)); @@ -1196,7 +1615,7 @@ class VstCompoundVerifier { def getEnumSetType(tref: TypeRef, enumType: EnumType) -> Type { if (enumType.setType == null) { errAtRange(tref.range()).EnumSetTooLarge(enumType, tref, enumType.enumDecl.cases.length); - return getErrorType(); + return tref.binding = getErrorType(); } return tref.binding = enumType.setType; } @@ -1723,12 +2142,13 @@ class TypeChecker(ERROR: ErrorGen, file: VstFile) extends VstVisitor pat.vdecl.isReadOnly = true; pat.vdecl.vtype = subtypeType; pat.vdecl.ssa = VstSsaVar.new(); + pat.subtypeType = subtypeType; return; } } return errAtExpr(ve).MatchError(Strings.format2("unresolved case %q.%s", vt.render, name)); } - mv.addVariantPattern(pat, vc); + mv.addCasePattern(pat, vc); } // Returns true if {candidate} is a (direct or transitive) subtype variant of {parent}. // Checks that candidate.qualifiers starts with parent's qualifier chain followed by parent's name. @@ -1750,11 +2170,23 @@ class TypeChecker(ERROR: ErrorGen, file: VstFile) extends VstVisitor if (ve.expr != null) { return errAtExpr(ve).MatchError(Strings.format1("match on type %q requires unqualified case", vt.render)); } - var vc = vt.lookupCase(ve.ident.name.image); + var name = ve.ident.name.image; + var vc = vt.lookupCase(name); if (vc == null) { - return errAtExpr(ve).MatchError(Strings.format2("unresolved case %q.%s", vt.render, ve.ident.name.image)); + // Not a named case; check if it names a direct subtype enum. + if (vt.enumDecl.subtypes != null) { + var subtypeDecl = vt.enumDecl.subtypes[name]; + if (subtypeDecl != null) { + pat.subtypeEnumType = EnumType.!(subtypeDecl.typeCon.singleton); + return; + } + } + return errAtExpr(ve).MatchError(Strings.format2("unresolved case %q.%s", vt.render, name)); } - mv.addEnumPattern(pat, vc); + if (Strings.equal(vc.name(), "_")) { + return errAtExpr(ve).MatchError(Strings.format1("cannot explicitly match '_ case' of enum %q; use a default arm instead", vt.render)); + } + mv.addCasePattern(pat, vc); } def visitEmpty(stmt: EmptyStmt, outer: Type) -> Type { return Void.TYPE; @@ -2783,19 +3215,12 @@ class MatchStmtVerifier(etype: Type, tc: TypeChecker) { pat.val = val; valueMap[val] = pat; } - def addVariantPattern(pat: MatchPattern, vc: VstCaseMember) { - if (covered[vc.tag] != null) { - MatchError(pat.expr.range(), "duplicate variant pattern", covered[vc.tag].expr.range()); - } - covered[vc.tag] = pat; - pat.val = Int.box(vc.decl.variantTag); - } - def addEnumPattern(pat: MatchPattern, vc: VstEnumCase) { - if (covered[vc.tag] != null) { - MatchError(pat.expr.range(), "duplicate case", covered[vc.tag].expr.range()); + def addCasePattern(pat: MatchPattern, vc: VstCaseMember) { + if (covered[vc.localTag] != null) { + MatchError(pat.expr.range(), "duplicate case", covered[vc.localTag].expr.range()); } - covered[vc.tag] = pat; - pat.val = Int.box(vc.tag); + covered[vc.localTag] = pat; + pat.val = Int.box(vc.runtimeTag()); } def addTypePattern(pat: MatchPattern, t: Type) { var cast = TypeSystem.newTypeCast(etype, t); @@ -2812,6 +3237,7 @@ class MatchStmtVerifier(etype: Type, tc: TypeChecker) { if (ct.classDecl.qualifiers != null) { // Subtype variant (e.g. x: B where type A.B). Generate a range // test via VARIANT_QUERY; coverage is satisfied by the _ default arm. + pat.subtypeType = ct; return; } // Named case: variantTag equals the local cases[] index after DFS @@ -2819,7 +3245,7 @@ class MatchStmtVerifier(etype: Type, tc: TypeChecker) { var tag = ct.classDecl.variantTag; var vc = variantType.classDecl.cases[tag]; pat.variantCase = vc; - return addVariantPattern(pat, vc); + return addCasePattern(pat, vc); } } } @@ -2832,6 +3258,8 @@ class MatchStmtVerifier(etype: Type, tc: TypeChecker) { def end(stmt: MatchStmt) { if (covered == null) return; if (default != null) { + // Open enums always have potential uncovered values (from subtypes); default is always reachable. + if (enumType != null && enumType.enumDecl.hasDefault) return; for (p in covered) { if (p == null) return; } @@ -2849,15 +3277,23 @@ class MatchStmtVerifier(etype: Type, tc: TypeChecker) { } } } + // If the enum has "case _", the match must have a default _ case. + if (enumType != null && enumType.enumDecl.hasDefault) { + MatchError(stmt.range(), + Strings.format1("match on open enum type %q requires a default case", enumType.render), + null); + return; + } var buf: StringBuilder; for (i < covered.length) { if (covered[i] == null) { var t = if(variantType == null, enumType, variantType); if (buf == null) buf = StringBuilder.new().put1("match on type %q does not cover:", t.render); - var name = if(variantType == null, + // Skip the _ case — it cannot be explicitly matched. + var caseName = if(variantType == null, enumType.enumDecl.cases[i].name(), variantType.classDecl.cases[i].name()); - buf.puts(" ").puts(name); + if (!Strings.equal(caseName, "_")) buf.puts(" ").puts(caseName); } } if (buf != null) MatchError(stmt.range(), buf.toString(), null); diff --git a/aeneas/src/vst/Vst.v3 b/aeneas/src/vst/Vst.v3 index 923e6122b..bd1ac66a0 100644 --- a/aeneas/src/vst/Vst.v3 +++ b/aeneas/src/vst/Vst.v3 @@ -5,7 +5,7 @@ class VstModule { def classes = Vector.new(); def components = Vector.new(); - def enums = Vector.new(); + def enums = Vector.new(); def exports = Vector.new(); def layouts = Vector.new(); def packings = Vector.new(); @@ -19,7 +19,7 @@ class VstModule { class VstFile extends ParsedFile { def classes = Vector.new(); def components = Vector.new(); - def enums = Vector.new(); + def enums = Vector.new(); def exports = Vector.new(); def layouts = Vector.new(); def packings = Vector.new(); @@ -137,9 +137,9 @@ class VstCompound extends Decl { def getParams() -> List { return if(params != null, params.list); } - def isEnum() -> bool { return VstEnum.?(this); } - def isVariant() -> bool { return variantTag > NON_VARIANT; } - def isVariantCase() -> bool { return variantTag >= 0; } + def isEnum() => false; + def isVariant() => false; + def isVariantCase() -> bool { return isVariant() && variantTag >= 0; } def isUnboxed() -> bool { var hints = repHints; @@ -173,12 +173,25 @@ class VstClass extends VstCompound { var qualifierTypeArgs: List>; // type args per qualifier, parallel to qualifiers var subtypes: HashMap; // direct subtype variants of this variant (filled by verifier) var subtypeOrder: List; // subtypes in reverse declaration order (complement to subtypes map) - var variantTagHi: int = -1; // inclusive upper bound of DFS tag range; = variantTag for leaf cases - var syntheticDefault: VstClass; // hidden "$default" case for types with no named cases + var tagLo: int = -1; // first DFS tag in subtype's range (for enum/variant subtype range checks) + var tagHi: int = -1; // inclusive upper bound of DFS tag range; = variantTag for leaf cases + var syntheticDefault: VstClass; // hidden "$default" case for types with no named cases + var hasDefault: bool; // true if has a "case _" (open type) + var superParams: bool; // if enum mentions 'super' params + var extraParams: VstList; // enum subtype-only params (new fields beyond root's) + var parentEnum: VstClass; // immediate parent in hierarchy (set by verifier) new(isPrivate: bool, name: Token, typeParams: List, params: VstList, superclass, superclause: SuperClause, members: List) super(isPrivate, name, typeParams, params, superclause, members) { } + def isEnum() => kind == Kind.ENUM; + def isVariant() => kind == Kind.VARIANT; + def findDefaultCase() -> VstClass { + for (c in cases) { + if (Strings.equal(c.name(), "_")) return c.decl; + } + return null; + } } // Parsed "component X { ... }" class VstComponent extends VstCompound { @@ -187,28 +200,6 @@ class VstComponent extends VstCompound { var recordIndex: int; new(isPrivate: bool, isThread, importName, name: Token, members: List) super(isPrivate, name, null, null, null, members) { } } -// Parsed "enum X { ... }" -class VstEnum extends VstCompound { - def cases: Array; - def tagType = V3.getTagType(cases.length); - - new(isPrivate: bool, name: Token, params: VstList, cases, members: List) - super(isPrivate, name, null, params, null, members) { - // XXX: move type creation somewhere else? - typeCon = TypeCon.new(token.image, Kind.ENUM, 0, TypeUtil.globalCache); - var enumType = EnumType.new(typeCon.uid, this, typeCon); - typeCon.singleton = enumType; - typeCon = typeCon; - if (cases.length <= Int.MAX_WIDTH) { - var setName = Strings.builderOf(token.image).puts(".set").toString(); - var typeCon = TypeCon.new(setName, Kind.ENUM_SET, 0, TypeUtil.globalCache); - var count = if(cases.length == 0, 1, cases.length); - var repType = Int.getType(false, count); - var enumSetType = EnumSetType.new(typeCon.uid, this, repType, typeCon); - enumType.setType = typeCon.singleton = enumSetType; - } - } -} // Parsed "layout L { ... }" class VstLayout extends VstCompound { def size: Literal; @@ -229,15 +220,6 @@ class VstPacking extends VstCompound { new(name: Token, pparams, expr, size) super(false, name, null, null, null, null) {} } -// Member of an enum. -class VstEnumCase extends VstMember { - def tag: int; - def args: VstList; - new(tag, name: Token, args) super(false, name) { } - def getType() -> Type { - return receiver.getDeclaredType(); - } -} // Member of a layout class VstLayoutField extends VstMember { def offset: Literal; @@ -269,17 +251,19 @@ class VariantDesugaring(isPrivate: bool, outer: VstIdent, params: def synthesizeMembers(fileName: string, line: int, col: int) -> List { var tagToken = Token.new(fileName, "tag", line, col); var nameToken = Token.new(fileName, "name", line, col); + var shortNameToken = Token.new(fileName, "shortName", line, col); var list: List; if (params == null) { - list = List.new(VstVariantTagMember.new(tagToken), list); - list = List.new(VstVariantNameMember.new(nameToken), list); + list = List.new(VstTagMember.new(tagToken), list); + list = List.new(VstNameMember.new(nameToken), list); + list = List.new(VstShortNameMember.new(shortNameToken), list); } return list; } def synthesizeTopLevelClass(members: List) -> VstClass { var decl = VstClass.new(isPrivate, outer.name, outer.list(), params, null, null, members); if (params != null) { - cases.put(VstCaseMember.new(0, outer.name, decl)); + cases.put(VstCaseMember.new(0, outer.name, decl, null)); decl.variantTag = 0; } else { decl.variantTag = ROOT_VARIANT; @@ -299,11 +283,37 @@ class VariantDesugaring(isPrivate: bool, outer: VstIdent, params: var buf = StringBuilder.new().puts(superclass.name.image); buf.putc('.'); synthetic.fullName = buf.puts(id.name.image).toString(); - var c = VstCaseMember.new(tag, id.name, synthetic); + var c = VstCaseMember.new(tag, id.name, synthetic, null); cases.put(c); return c; } } +// A helper class for desugaring enums into VstClasses. +class EnumDesugaring(isPrivate: bool, id: VstIdent, params: VstList) { + var cases = Vector.new(); + var hasDefault = false; + def synthesizeMembers(fileName: string, line: int, col: int) -> List { + var tagToken = Token.new(fileName, "tag", line, col); + var nameToken = Token.new(fileName, "name", line, col); + var shortNameToken = Token.new(fileName, "shortName", line, col); + var list: List; + list = List.new(VstTagMember.new(tagToken), list); + list = List.new(VstNameMember.new(nameToken), list); + list = List.new(VstShortNameMember.new(shortNameToken), list); + return list; + } + def synthesizeTopLevelClass(members: List) -> VstClass { + var decl = VstClass.new(isPrivate, id.name, null, params, null, null, members); + decl.kind = Kind.ENUM; + decl.cases = cases.extract(); + decl.hasDefault = hasDefault; + return decl; + } + def addCase(c: VstCaseMember) { + if (Strings.equal(c.name(), "_")) hasDefault = true; + cases.put(c); + } +} // A member of a class or component class VstMember extends Decl { @@ -322,30 +332,42 @@ class VstMember extends Decl { return buf.puts(token.image); } } -// Desugared case member from a variant. +// Desugared case member from a variant/enum. class VstCaseMember extends VstMember { - def tag: int; + def localTag: int; // local parse-order index (for covered[] array in match) def decl: VstClass; + def args: VstList; // for enums + var enumTag: int; // runtime tag; initialized to localTag, may be reassigned by DFS + var members: List; // per-case method overrides - new(tag, name: Token, decl) super(false, name) { } + new(localTag, name: Token, decl, args) super(false, name) { + enumTag = localTag; + } + def runtimeTag() -> int { + return if(decl != null, decl.variantTag, enumTag); + } def getType() -> Type { return if(decl == null, receiver.declType, decl.declType); } } -// Synthesized .tag member from a variant. -class VstVariantTagMember extends VstMember { +// Synthesized .tag member from a variant/enum. +class VstTagMember extends VstMember { new(name: Token) super(false, name) { } def getType() -> Type { match (receiver) { x: VstClass => return x.tagType; - x: VstEnum => return x.tagType; _ => return null; } } } -// Synthesized .name member from a variant. -class VstVariantNameMember extends VstMember { +// Synthesized .name member from a variant/enum. +class VstNameMember extends VstMember { + new(name: Token) super(false, name) { } + def getType() -> Type { return V3.stringType; } +} +// Synthesized .shortName member from a variant/enum. +class VstShortNameMember extends VstMember { new(name: Token) super(false, name) { } def getType() -> Type { return V3.stringType; } } @@ -399,6 +421,7 @@ class VstMethod extends VstMember { var typeEnv: TypeEnv; var root: VstMethod; var importName: Token; + var enumCaseIrs: Array; // per-case override IrMethods, indexed by enumTag new(isPrivate: bool, name: Token, typeParams, func) super(isPrivate, name) { } @@ -721,6 +744,8 @@ class MatchPattern(expr: Expr, params: VstList) { var variantCase: VstCaseMember; // if this is a match on a variant case var vdecl: VarDecl; // if this is a type case (including variant subtype patterns) var val: Val; + var subtypeType: Type; // set when pattern names a variant subtype (second grammar rule) + var subtypeEnumType: Type; // set when pattern names a subtype enum (e.g. E1.More) def decl() -> VstClass { return variantCase.decl; } } // continue; @@ -973,7 +998,7 @@ type VarBinding { case ComponentMethod(member: VstMethod, typeArgs: TypeArgs); case VariantCase(receiver: Type, member: VstCaseMember); case VariantField(receiver: Type, member: VstField); // writable if unboxed - case EnumConst(member: VstEnumCase); + case EnumConst(member: VstCaseMember); case Inst(op: Operator, facts: Fact.set); // instantiate {op} case Apply(op: Operator, facts: Fact.set); // apply {op} to receiver case Partial(op: Operator, facts: Fact.set); // partially apply {op} to receiver @@ -1064,10 +1089,11 @@ type VstOperator { case FloatFraction; // desugared to (u23|u52).!(u32|u64.view(float|double)) case VariantGetName; // desugared to ArrayGetElem(#names, VariantGetTag(x)) + case VariantGetShortName; // desugared to ArrayGetElem(#shortNames, VariantGetTag(x)) case EnumGetParam(field: VstField); // desugared to ArrayGetElem(#field, x) - case EnumSetHas(vcase: VstEnumCase); // desugared to (1 << k) & x != 0 + case EnumSetHas(vcase: VstCaseMember); // desugared to (1 << k) & x != 0 case EnumSetUnion; // desugared to x | y case EnumSetIntersect; // desugared to x & y case EnumSetSubtract; // desugared to x & ~(x & y) diff --git a/aeneas/src/wasm/WasmCodeGen.v3 b/aeneas/src/wasm/WasmCodeGen.v3 index 0072197ee..bbe31b349 100644 --- a/aeneas/src/wasm/WasmCodeGen.v3 +++ b/aeneas/src/wasm/WasmCodeGen.v3 @@ -323,7 +323,7 @@ class WasmCodeGen extends SsaMachGen { dfnAll(i); } else { var rt = i.input1().getType(); - if (rt == m.receiver) { + if (rt == m.receiver || EnumType.?(m.receiver)) { start = 1; // just push the receiver as is dfnAll(i); } else { @@ -361,7 +361,7 @@ class WasmCodeGen extends SsaMachGen { dfnAll(i); } else { var rt = i.input1().getType(); - if (rt == m.receiver) { + if (rt == m.receiver || EnumType.?(m.receiver)) { start = 1; // just push the receiver as is dfnAll(i); } else { @@ -385,7 +385,6 @@ class WasmCodeGen extends SsaMachGen { var funcType = FuncType.!(i.op.typeArgs[0]); var sig = funcType.sig(); var sigIndex = wasmGc.addSig(Void.TYPE, sig); - dfnAll(i); if (useShadowStack) refmap(null); useAll(i.inputs, 1); // use arguments, except target @@ -1635,8 +1634,12 @@ class WasmCodeGen extends SsaMachGen { // just load all arguments except the first one and call the target. // WasmGc indirect adapters are also used for methods with receivers, // which must load and cast the first argument. + // Enum methods: no receiver to drop/cast; load all params directly. // ============ indirect adapter code ======================== - if (GC) { + var loopStart = 1; // default: skip first param (Oop/funcref receiver) + if (EnumType.?(m.receiver)) { + loopStart = 0; // enum: no receiver to skip + } else if (GC) { var rcvr = m.receiver; if (rcvr != Void.TYPE && !V3.isComponent(rcvr)) { // get the receiver and cast it @@ -1647,7 +1650,7 @@ class WasmCodeGen extends SsaMachGen { } } - for (i = 1; i < m.ssa.params.length; i++) { + for (i = loopStart; i < m.ssa.params.length; i++) { b(WasmOp.GET_LOCAL.opcode); w.put_sleb32(i); } b(WasmOp.CALL.opcode); @@ -1669,9 +1672,11 @@ class WasmCodeGen extends SsaMachGen { // arguments to the target function // ============ dispatch adapter code ======================== b(WasmOp.GET_LOCAL.opcode); w.put_sleb32(0); - w.put_b16be(WasmExtOp.REF_CAST_NULL.extopcode); - var wht = wasmGc.gcTypeTable.addHeapType(m.receiver); - w.put_sleb32(int.!(wht.index - 1)); + if (!EnumType.?(m.receiver)) { + w.put_b16be(WasmExtOp.REF_CAST_NULL.extopcode); + var wht = wasmGc.gcTypeTable.addHeapType(m.receiver); + w.put_sleb32(int.!(wht.index - 1)); + } for (i = 1; i < m.ssa.params.length; i++) { b(WasmOp.GET_LOCAL.opcode); w.put_sleb32(i); } diff --git a/aeneas/src/wasm/WasmGcTarget.v3 b/aeneas/src/wasm/WasmGcTarget.v3 index d7fe84d1c..799033884 100644 --- a/aeneas/src/wasm/WasmGcTarget.v3 +++ b/aeneas/src/wasm/WasmGcTarget.v3 @@ -351,8 +351,9 @@ class WasmGcTarget extends Target { wasm.indirectAdapterFor[meth] = findex; wasm.indirectAdapterIndexFor[meth] = i + 1; // + 1 to skip the null entry wasm.indirectAdapterVector.put(meth); - var sigIdx = wasm.addSig(Oop.TYPE, meth.sig); - var fi = FunctionInfo.new(FunctionInfoKind.Indirect, Oop.TYPE, meth.sig, sigIdx, meth, findex); + var adapterRecv = if(EnumType.?(meth.receiver), Void.TYPE, Oop.TYPE); + var sigIdx = wasm.addSig(adapterRecv, meth.sig); + var fi = FunctionInfo.new(FunctionInfoKind.Indirect, adapterRecv, meth.sig, sigIdx, meth, findex); table0Infos.put(fi); functionInfos.put(fi); if (WasmGcComponent.printSection.FUNCTION || @@ -410,8 +411,9 @@ class WasmGcTarget extends Target { wasm.requestIndirectAdapter(m); var findex = functionInfos.length; wasm.indirectAdapterFor[m] = findex; - var sigIdx = wasm.addSig(Oop.TYPE, m.sig); - var fi = FunctionInfo.new(FunctionInfoKind.Indirect, Oop.TYPE, m.sig, sigIdx, m, findex); + var adapterRecv = if(EnumType.?(m.receiver), Void.TYPE, Oop.TYPE); + var sigIdx = wasm.addSig(adapterRecv, m.sig); + var fi = FunctionInfo.new(FunctionInfoKind.Indirect, adapterRecv, m.sig, sigIdx, m, findex); table0Infos.put(fi); wasm.indirectAdapterIndexFor[m] = table0Infos.length; wasm.indirectAdapterVector.put(m); @@ -452,7 +454,8 @@ class WasmGcTarget extends Target { wasm.dispatchTableFor[mtable] = tableNum; var root = mtable.root; - var sigIdx = wasm.addSig(root.receiver, root.sig); // make sure signature is present + var dispRecv = if(EnumType.?(root.receiver), Void.TYPE, root.receiver); + var sigIdx = wasm.addSig(dispRecv, root.sig); // make sure signature is present var superIdx = root.machIndex; // superIdx *can* be -1 (e.g., for an ADT with an empty-body default for a method) var funcInfo: FunctionInfo = if(superIdx < 0, null, functionInfos[superIdx]); @@ -1315,7 +1318,8 @@ class WasmGcProgram extends WasmProgram { } // override def getSigIndex(m: IrMethod) -> u32 { - var sig = getSig(m.receiver, m.sig); + var recv = if(EnumType.?(m.receiver), Void.TYPE, m.receiver); + var sig = getSig(recv, m.sig); var wft = gcTypeTable.addSignature(sig); var index: u32 = if(wft == null, 0, wft.index); if (index == 0) context.fail1("method signature not found: %q", sig.funcType().render); @@ -1408,7 +1412,9 @@ class WasmGcProgram extends WasmProgram { return -1; } def addMethodGc(m: IrMethod, infos: Vector, imported: bool) { - var sigIdx = addSig(m.ssa.params[0].vtype, m.sig); + // For enum methods, the tag is already the first param in m.sig (no receiver prepend needed) + var recv = if(EnumType.?(m.receiver), Void.TYPE, m.ssa.params[0].vtype); + var sigIdx = addSig(recv, m.sig); m.machIndex = infos.length; var fi = FunctionInfo.new(if(imported, FunctionInfoKind.Import, FunctionInfoKind.Method), m.receiver, m.sig, sigIdx, m, infos.length); infos.put(fi); diff --git a/aeneas/src/wasm/WasmTarget.v3 b/aeneas/src/wasm/WasmTarget.v3 index c5e113c91..8314b2c93 100644 --- a/aeneas/src/wasm/WasmTarget.v3 +++ b/aeneas/src/wasm/WasmTarget.v3 @@ -521,7 +521,7 @@ class WasmProgram(mach: MachProgram, context: SsaContext, test: bool, wasmGc: bo } def getSigIndex(m: IrMethod) -> u32 { var sig = m.sig; - if (!V3.isComponent(m.receiver)) { + if (!V3.isComponent(m.receiver) && !EnumType.?(m.receiver)) { sig = Signature.new(null, Arrays.prepend(m.ssa.params[0].vtype, sig.paramTypes), sig.returnTypes); } return typeTable.add(sig); @@ -606,7 +606,8 @@ class WasmProgram(mach: MachProgram, context: SsaContext, test: bool, wasmGc: bo def setMethod(index: int, m: IrMethod, kind: WasmFuncKind) -> int { if (m != null) { m.machIndex = index; - addSig(m.ssa.params[0].vtype, m.sig); + var recv = if(EnumType.?(m.receiver), Void.TYPE, m.ssa.params[0].vtype); + addSig(recv, m.sig); } functions.set(index, (m, kind)); functionCursor = functions.length; diff --git a/apps/vctags/vctags.v3 b/apps/vctags/vctags.v3 index 86d5623d1..c023a78e7 100644 --- a/apps/vctags/vctags.v3 +++ b/apps/vctags/vctags.v3 @@ -124,7 +124,7 @@ component vctags { TagKind.ENUM, edecl.token); file.tags.put(tag); generateCompoundMemberTags(file, edecl, CompoundKind.ENUM); - generateDeclTags(file, Arrays.map(edecl.cases, VstMember.!), CompoundKind.ENUMCASE); + generateDeclTags(file, Arrays.map(edecl.cases, VstMember.!), CompoundKind.ENUMCASE); generateParamTags(file, edecl.params, CompoundKind.CLASS); } diff --git a/ci/Makefile b/ci/Makefile index 8259bcb69..e9c987f35 100644 --- a/ci/Makefile +++ b/ci/Makefile @@ -44,8 +44,8 @@ x86-runs: $(X86_RUNS) X86_64_RUNS = x86-64 x86-64-wfts x86-64-O1 x86-64-O1-wfts x86-64-fp x86-64-fp-wfts \ - x86-64-O2 x86-64-O2-wfts x86-64-uv x86-64-uv-wfts \ - x86-64-uv-O2 x86-64-uv-O2-wfts \ + x86-64-O2 x86-64-O2-wfts x86-64-O3 x86-64-O3-wfts x86-64-uv x86-64-uv-wfts \ + x86-64-uv-O2 x86-64-uv-O2-wfts x86-64-uv-O3 x86-64-uv-O3-wfts \ x86-64-runs: $(X86_64_RUNS) @@ -55,17 +55,17 @@ X86_64_DARWIN_RUNS = x86-64-darwin-bootstrap x86-64-darwin-current x86-64-darwin-runs: $(X86_64_DARWIN_RUNS) -JVM_RUNS = jvm jvm-O2 jvm-uv +JVM_RUNS = jvm jvm-O2 jvm-O3 jvm-uv jvm-runs: $(JVM_RUNS) -WASM_RUNS = wasm wasm-wfts wasm-O2 wasm-O2-wfts +WASM_RUNS = wasm wasm-wfts wasm-O2 wasm-O2-wfts wasm-O3 wasm-O3-wfts wasm-runs: $(WASM_RUNS) -WASM_GC_RUNS = wasm-gc wasm-gc-O2 wasm-gc-og wasm-gc-O2-og wasm-gc-rt wasm-gc-O2-rt +WASM_GC_RUNS = wasm-gc wasm-gc-O2 wasm-gc-O3 wasm-gc-og wasm-gc-O2-og wasm-gc-O3-og wasm-gc-rt wasm-gc-O2-rt wasm-gc-O3-rt wasm-gc-runs: $(WASM_GC_RUNS) @@ -108,6 +108,12 @@ x86-64-O2: x86-64-O2-wfts: CI_DIR=ci/$@ bash ./linux/build-test-x86_64.sh -O2 -wfts > $@-out.txt 2>&1 +x86-64-O3: + CI_DIR=ci/$@ bash ./linux/build-test-x86_64.sh -O3 > $@-out.txt 2>&1 + +x86-64-O3-wfts: + CI_DIR=ci/$@ bash ./linux/build-test-x86_64.sh -O3 -wfts > $@-out.txt 2>&1 + x86-64-uv: CI_DIR=ci/$@ bash ./linux/build-test-x86_64.sh -unbox-variants > $@-out.txt 2>&1 @@ -120,6 +126,12 @@ x86-64-uv-O2: x86-64-uv-O2-wfts: CI_DIR=ci/$@ bash ./linux/build-test-x86_64.sh -unbox-variants -O2 -wfts > $@-out.txt 2>&1 +x86-64-uv-O3: + CI_DIR=ci/$@ bash ./linux/build-test-x86_64.sh -unbox-variants -O3 > $@-out.txt 2>&1 + +x86-64-uv-O3-wfts: + CI_DIR=ci/$@ bash ./linux/build-test-x86_64.sh -unbox-variants -O3 -wfts > $@-out.txt 2>&1 + # # x86-64-darwin # @@ -139,6 +151,9 @@ jvm: jvm-O2: CI_DIR=ci/$@ bash ./jvm/build-test-jvm.sh -O2 > $@-out.txt 2>&1 +jvm-O3: + CI_DIR=ci/$@ bash ./jvm/build-test-jvm.sh -O3 > $@-out.txt 2>&1 + jvm-uv: CI_DIR=ci/$@ bash ./jvm/build-test-jvm.sh -unbox-variants > $@-out.txt 2>&1 @@ -157,6 +172,12 @@ wasm-O2: wasm-O2-wfts: CI_DIR=ci/$@ bash ./wasm/build-test-wasm.sh -O2 -wfts > $@-out.txt 2>&1 +wasm-O3: + CI_DIR=ci/$@ bash ./wasm/build-test-wasm.sh -O3 > $@-out.txt 2>&1 + +wasm-O3-wfts: + CI_DIR=ci/$@ bash ./wasm/build-test-wasm.sh -O3 -wfts > $@-out.txt 2>&1 + # # WASM GC (wfts always enabled) # @@ -166,14 +187,23 @@ wasm-gc: wasm-gc-O2: CI_DIR=ci/$@ bash ./wasm-gc/build-test-wasm-gc.sh -O2 > $@-out.txt 2>&1 +wasm-gc-O3: + CI_DIR=ci/$@ bash ./wasm-gc/build-test-wasm-gc.sh -O3 > $@-out.txt 2>&1 + wasm-gc-og: CI_DIR=ci/$@ bash ./wasm-gc/build-test-wasm-gc.sh -wasm-gc-one-group > $@-out.txt 2>&1 wasm-gc-O2-og: CI_DIR=ci/$@ bash ./wasm-gc/build-test-wasm-gc.sh -O2 -wasm-gc-one-group > $@-out.txt 2>&1 +wasm-gc-O3-og: + CI_DIR=ci/$@ bash ./wasm-gc/build-test-wasm-gc.sh -O3 -wasm-gc-one-group > $@-out.txt 2>&1 + wasm-gc-rt: CI_DIR=ci/$@ bash ./wasm-gc/build-test-wasm-gc.sh -wasm-gc-use-ref-test > $@-out.txt 2>&1 wasm-gc-O2-rt: CI_DIR=ci/$@ bash ./wasm-gc/build-test-wasm-gc.sh -O2 -wasm-gc-use-ref-test > $@-out.txt 2>&1 + +wasm-gc-O3-rt: + CI_DIR=ci/$@ bash ./wasm-gc/build-test-wasm-gc.sh -O3 -wasm-gc-use-ref-test > $@-out.txt 2>&1 diff --git a/doc/grammar-claude.md b/doc/grammar-claude.md index 6e6af6471..949dc30da 100644 --- a/doc/grammar-claude.md +++ b/doc/grammar-claude.md @@ -76,9 +76,77 @@ In match patterns, unqualified subtype names (`Bar =>` or `b: Bar =>`) automatic ``` -EnumDecl ::= id EnumParams? '{' EnumCase* '}' -EnumCase ::= id ['(' Expr,* ')'] ','? +EnumDecl ::= DottedId EnumParams? '{' EnumCase* (';' EnumMethod*)? '}' +EnumCase ::= id ['(' Expr,* ')'] EnumCaseBody? ','? // named case + | '_' EnumCaseBody? ','? // default case: optional, must be last, at most one +EnumCaseBody ::= '{' EnumCaseMethod* '}' +EnumCaseMethod ::= ['private'] 'def' DefDef +EnumMethod ::= ['private'] 'def' DefDef // shared by all cases; may be overridden per-case + +EnumParams ::= '(' 'super' ')' // inherit parent's params + | '(' 'super' ',' ParamDecl,+ ')' // inherit parent's params + add new fields + | '(' ParamDecl,* ')' // declare params (root or restate + optional extras) + +``` + +### Enum subtype constraints (checked by verifier) + +An `EnumDecl` whose `DottedId` is a plain `id` is a **root** (top-level) enum. + +An `EnumDecl` whose `DottedId` has the form `D.T` (one or more dots) declares a **subtype enum**: + +- The first identifier in `D` must name a root enum. +- Every intermediate identifier in `D` must name an enum that is a direct subtype of the previous one (transitively established by prior declarations). +- The immediate parent (the enum named by all of `D`) must have a `case _`. +- `T` must not clash with any named `case id` of the immediate parent. +- `D.T` may be declared at most once (among all files of the program). + +### Enum subtype parameter rules + +A subtype's **effective parameters** are its parent's effective parameters plus any extra parameters the subtype itself declares. The root enum's effective parameters are simply its own declared parameters. + +Subtypes may reference their parent's effective parameters in several ways: + +- **Form 1 (restate)**: `enum E.S(x: int)` — restates the parent's effective params by name and type. May also add extra params: `enum E.S(x: int, f: float)`. +- **Form 2 (super)**: `enum E.S(super)` — inherits the parent's effective params without restating them. +- **Form 2+add (super + extras)**: `enum E.S(super, f: float)` — inherits parent's effective params and adds new fields. +- **Form 3 (implicit)**: `enum E.S` — no param list; parent's effective params are inherited implicitly. + +In all forms, each case must provide argument values for all effective parameters (parent's effective params + own extra params, in order). + +In multi-level hierarchies, `super` refers to the immediate parent's effective parameters, which includes the root's parameters and all intermediate ancestors' extra parameters. For example, given `enum E(x: int)` and `enum E.S(super, y: int)`, a grandchild `enum E.S.T(super, z: int)` has effective parameters `(x, y, z)` and each case must provide all three values. + +Subtypes may **not** declare params if the parent has no effective params. Using `super` when the parent has no effective params is an error. + +### Enum methods + +An `EnumMethod` declared after the `;` separator is shared by all cases. It may be overridden per-case by placing a `def` with the same name and signature inside an `EnumCaseBody`. + +- An override must have the same parameter types and return type as the root method. +- Dispatch on a variable of enum type is virtual: the override for the specific case is called at runtime. +- Static dispatch on a known case (e.g. `E.A.m()`) calls the override directly if one exists. + +### Enum method inheritance + +- Methods declared on a parent enum are inherited by all subtype enums (transitively). +- A subtype enum may override an inherited method by declaring a method with the same name and signature after its own `;` separator. +- Individual cases of a subtype may further override methods via `EnumCaseBody`. +- All virtual dispatch goes through the root enum's dispatch table, regardless of where the override is declared. + + +### Enum match pattern semantics + +When matching on an expression of enum type `E`, a match pattern may name: + +- A **named case** of `E` (e.g. `X` where `E` has `case X`) — matched by tag +- A **subtype enum** of `E` (e.g. `S` where `E.S` is a subtype) — matched by tag range + +Only the **unqualified** name is legal in a match pattern. For example, if `E.S` is a subtype of `E`, write `S`, not `E.S`. + +A match on an enum type `E` that has `case _` must always include a `_` arm regardless of which named cases or subtypes are listed. + +``` PackingDecl ::= id '(' PackingParam,* ')' ':' int '=' PackingExpr ';' PackingParam ::= id ':' int PackingExpr ::= BitPattern // 0b... @@ -135,7 +203,8 @@ TypeParam ::= id ClassParams ::= '(' ParamDecl,* ')' // class constructor params (typed, def read-only) MethodParams ::= '(' ParamDecl,* ')' // method params (optionally typed) VariantCaseParams ::= '(' ParamDecl,* ')' // like enum params (typed, def read-only) -EnumParams ::= '(' ParamDecl,* ')' // typed, immutable +EnumParams ::= '(' 'super' [',' ParamDecl,+] ')' // super form (with optional extras) + | '(' ParamDecl,* ')' // typed, immutable ParamDecl ::= ['var'] id [':' TypeRef] ``` diff --git a/doc/tutorial/EnumMethods.md b/doc/tutorial/EnumMethods.md new file mode 100644 index 000000000..5841f519c --- /dev/null +++ b/doc/tutorial/EnumMethods.md @@ -0,0 +1,73 @@ +# Enum Methods + +Enums can have methods, declared after a `;` separator following the cases. +Methods are shared by all cases of the enum. + +``` +enum Planet(mass: double, radius: double) { + MERCURY(3.303e+23, 2.4397e6), + VENUS(4.869e+24, 6.0518e6), + EARTH(5.976e+24, 6.37814e6); + + def surfaceGravity() -> double { + return 6.67300E-11 * mass / (radius * radius); + } +} +var g = Planet.EARTH.surfaceGravity(); +``` + +## Per-case method overrides + +Individual cases can override an enum method by providing their own definition inside `{ }` braces. +When calling the method on a variable of the enum type, the correct override is dispatched at runtime. + +``` +enum Expr { + ADD { def eval(a: int, b: int) -> int { return a + b; } }, + SUB { def eval(a: int, b: int) -> int { return a - b; } }, + MUL { def eval(a: int, b: int) -> int { return a * b; } }; + + def eval(a: int, b: int) -> int { return 0; } // default +} +def compute(op: Expr, x: int, y: int) -> int { + return op.eval(x, y); // virtual dispatch: calls the override for the specific case +} +``` + +The `_` (default) case can also have method overrides, which apply to any value that doesn't have a more specific override. + +## Enum method closures + +Enum methods can be used as closures, just like methods on classes or variants. +The closure captures the enum value and dispatches correctly when called. + +``` +enum Op { + INC { def apply(x: int) -> int { return x + 1; } }, + DEC { def apply(x: int) -> int { return x - 1; } }; + + def apply(x: int) -> int { return x; } +} +def transform(op: Op, value: int) -> int { + var f = op.apply; // create a closure + return f(value); // calls the correct override +} +``` + +## Subtype enum method inheritance + +Subtype enums inherit methods from their parent. +A subtype can override an inherited method, and individual cases of a subtype can override it further. + +``` +enum Animal { DOG, CAT, _; def speak() -> int { return 0; } } +enum Animal.Exotic { + PARROT { def speak() -> int { return 2; } }, + SNAKE; + + def speak() -> int { return 1; } // override for all Exotic cases +} +``` + +Here `Animal.Exotic.PARROT.speak()` returns `2` (per-case override), `Animal.Exotic.SNAKE.speak()` returns `1` (subtype override), and `Animal.DOG.speak()` returns `0` (root default). +All dispatch goes through the root enum's dispatch table, so a variable of type `Animal` will dispatch correctly regardless of whether the value is a root case or a subtype case. diff --git a/doc/tutorial/EnumSets.md b/doc/tutorial/EnumSets.md index 359d964f9..f44f0d00f 100644 --- a/doc/tutorial/EnumSets.md +++ b/doc/tutorial/EnumSets.md @@ -134,7 +134,22 @@ var x: E; var y = foo(x); // E value is automatically promoted to E.set ``` +## Enum sets for open enums + +Open enums (those with `case _`) and their subtype enums share a single set type across the entire hierarchy. +The set type is sized to accommodate all cases in the hierarchy, including those from subtypes. + +``` +enum Color { RED, GREEN, BLUE, _ } +enum Color.Pastel { PINK, LAVENDER, MINT } + +var warm: Color.set = Color.RED | Color.Pastel.PINK; // mix root and subtype cases +var isPink = warm.PINK; // check for subtype case membership +``` + +Subtype enum values promote to the root's set type, so all set operations work uniformly across the hierarchy. + ## Implementation limits -In the current implementation of Virgil in this repository, enum set types are available for all enum declarations with 64 or fewer values. +In the current implementation of Virgil in this repository, enum set types are available for all enum declarations with 64 or fewer values (including all cases across the hierarchy for open enums). This limitation is due to a simple approach of rewriting these operations to integer operations early in compilation and will be lifted in the future by tuples of as many integers as necessary. diff --git a/doc/tutorial/Enums.md b/doc/tutorial/Enums.md index 89d5bcdff..fef22e489 100644 --- a/doc/tutorial/Enums.md +++ b/doc/tutorial/Enums.md @@ -101,3 +101,140 @@ In fact, in this example, we also subsumed the `isWorkday()` method we wrote by The Virgil compiler will still represent the enum value as a small integer under the hood, and field access will be a simple array access that uses the enum value as the index. This will typically be a single machine instruction; it's hard to beat that in terms of efficiency! + +## Open enums + +An enum may include a `case _` to mark it as *open*, or *extensible*. +An open enum accepts values from **subtype enums** (declared elsewhere) in addition to its own named cases. + +``` +enum Color { RED, GREEN, BLUE, _ } +``` + +The `case _` must be the last case in the enum and there can be at most one per enum. + +## Subtype enums + +A *subtype enum* is declared with a dotted name `E.S`, making it an extension of an existing open enum. +The parent must have a `case _`. + +``` +enum Color { RED, GREEN, BLUE, _ } +enum Color.Pastel { PINK, LAVENDER, MINT } +``` + +Now `Color.Pastel.PINK`, `Color.Pastel.LAVENDER`, and `Color.Pastel.MINT` are valid `Color` values. +A variable of type `Color` can hold any case, including those from subtype enums. + +``` +var c: Color = Color.Pastel.PINK; // valid: Pastel is a subtype of Color +``` + +Subtype enums can themselves be open (with `case _`) and have their own subtypes, forming hierarchies of arbitrary depth. + +``` +enum Color { RED, GREEN, BLUE, _ } +enum Color.Pastel { PINK, LAVENDER, _ } +enum Color.Pastel.Spring { CORAL, PEACH } +``` + +## Matching open enums + +When matching a value of an open enum type, subtype enums may be named directly as match arms. +A match on an open enum **always** requires a `_` arm, since new subtypes may be added independently. + +``` +enum Color { RED, GREEN, BLUE, _ } +enum Color.Pastel { PINK, LAVENDER, MINT } +def describe(c: Color) -> int { + match (c) { + RED => return 0; + GREEN => return 1; + BLUE => return 2; + Pastel => return 3; // matches any Color.Pastel case + _ => return -1; // required: covers any other subtype + } +} +``` + +Subtype names are written *unqualified* in match patterns -- `Pastel` rather than `Color.Pastel`. + +## Open enums with fields + +Open enums can have fields, just like regular enums. +Subtype enums inherit the parent's fields, and each case must provide values for them. + +There are several ways to declare a subtype's relationship to the parent's fields: + +``` +enum Shape(sides: int) { TRIANGLE(3), SQUARE(4), _ } + +// Form 1: restate the parent's parameters +enum Shape.Round(sides: int) { CIRCLE(0) } + +// Form 2: use the 'super' keyword +enum Shape.Polygon(super) { PENTAGON(5), HEXAGON(6) } + +// Form 3: implicit inheritance (no parameter list) +enum Shape.Special { STAR(10) } +``` + +In all forms, each case must provide argument values for all of the parent's effective fields. + +## Subtypes with additional fields + +Subtypes can also declare **new fields** beyond the parent's. +There are two ways to do this: + +``` +enum Vehicle(wheels: int) { CAR(4), BIKE(2), _ } + +// Using 'super' + extra fields +enum Vehicle.Electric(super, range: int) { TESLA(4, 300), EBIKE(2, 50) } + +// Restating parent fields + extra fields +enum Vehicle.Flying(wheels: int, altitude: int) { HELICOPTER(0, 5000) } +``` + +The parent's fields are always first in the argument list, followed by any extra fields. + +New fields are accessible only on values typed as the subtype: + +``` +var v: Vehicle = Vehicle.Electric.TESLA; +var w = v.wheels; // 4: inherited field, accessible on Vehicle + +var e: Vehicle.Electric = Vehicle.Electric.TESLA; +var r = e.range; // 300: new field, only accessible on Vehicle.Electric +``` + +## Multi-level field inheritance + +Extra fields are inherited through the hierarchy. +In a multi-level hierarchy, `super` refers to the immediate parent's *effective* fields -- the root's fields plus all intermediate ancestors' extra fields. + +``` +enum Animal(legs: int) { DOG(4), BIRD(2), _ } +enum Animal.Pet(super, name: string) { CAT(4, "cat"), _ } +enum Animal.Pet.Exotic(super, origin: string) { PARROT(2, "parrot", "Brazil") } +``` + +Here `Animal.Pet.Exotic` has three effective fields: `legs` (from root), `name` (from `Animal.Pet`), and `origin` (its own). +Each case must provide values for all three, in order. + +All inherited fields are accessible on subtype-typed values: + +``` +var p: Animal.Pet.Exotic = Animal.Pet.Exotic.PARROT; +var l = p.legs; // 2: from root Animal +var n = p.name; // "parrot": from intermediate Animal.Pet +var o = p.origin; // "Brazil": own field +``` + +## The `name` and `shortName` fields + +For subtype enum cases, the `name` field includes the subtype path. +For example, `Color.Pastel.PINK.name` returns `"Pastel.PINK"`. +The `shortName` field gives only the case name without the subtype prefix: `Color.Pastel.PINK.shortName` returns `"PINK"`. + +Enums can also have [methods](EnumMethods.md), including per-case overrides with virtual dispatch. diff --git a/doc/virgil-grammar.ebnf b/doc/virgil-grammar.ebnf index ba624ca81..fc1e18a1c 100644 --- a/doc/virgil-grammar.ebnf +++ b/doc/virgil-grammar.ebnf @@ -4,7 +4,8 @@ ToplevelDecl ::= ClassDecl | ComponentDecl | VariantDecl | LayoutDecl | EnumDecl ClassDecl ::= "class" IdentParam ( "(" VarParamDecls? ")" )? ( "extends" TypeRef TupleExpr? )? RepHints "{" Member* "}" ComponentDecl ::= "import"? "component" IDENTIFIER "{" Member* "}" VariantDecl ::= "type" ( IdentParam "." )* IdentParam ( "(" ParamDecls? ")" )? RepHints ( ";" | "{" VariantMember* "}" ) -EnumDecl ::= "enum" IDENTIFIER ( "(" ParamDecls? ")" )? "{" EnumCases? "}" +EnumDecl ::= "enum" ( IDENTIFIER "." )* IDENTIFIER EnumParams? "{" EnumCases? "}" +EnumParams ::= "(" "super" ")" | "(" "super" "," ParamDecls ")" | "(" ParamDecls? ")" LayoutDecl ::= "layout" IDENTIFIER RepHints "{" LayoutField* LayoutSize "}" LayoutField ::= "+" INTEGER IDENTIFIER ":" TypeRef RepHints ";" LayoutSize ::= "=" INTEGER ";" @@ -17,7 +18,7 @@ NewMember ::= "private"? "new" "(" NewParamDecls? ")" ( ":"? "super" TupleExpr DefMethod ::= "private"? "def" (IndexMethod | Method) VariantCase ::= "case" IDENTIFIER ( "(" ParamDecls? ")" )? RepHints ( ";" | "{" DefMethod* "}" ) | "case" "_" RepHints ( ";" | "{" DefMethod* "}" ) -EnumCase ::= IDENTIFIER ( "(" ( Expr ( "," Expr )* )? ")" )? +EnumCase ::= IDENTIFIER ( "(" ( Expr ( "," Expr )* )? ")" )? | "_" EnumCases ::= EnumCase ( "," EnumCase )* VarParamDecl ::= "var"? IDENTIFIER ":" TypeRef diff --git a/test/enums/enum_closure00.v3 b/test/enums/enum_closure00.v3 new file mode 100644 index 000000000..cfe7460f7 --- /dev/null +++ b/test/enums/enum_closure00.v3 @@ -0,0 +1,8 @@ +//@execute 0=42; 1=42 +// Basic enum method closure (non-overridden) +enum E { A, B; def m() -> int { return 42; } } +def main(i: int) -> int { + var e: E = if(i == 0, E.A, E.B); + var f = e.m; + return f(); +} diff --git a/test/enums/enum_closure01.v3 b/test/enums/enum_closure01.v3 new file mode 100644 index 000000000..be2a301c1 --- /dev/null +++ b/test/enums/enum_closure01.v3 @@ -0,0 +1,8 @@ +//@execute 0=1; 1=0; 2=2 +// Overridden method closure with virtual dispatch +enum E { A { def m() -> int { return 1; } }, B, C { def m() -> int { return 2; } }; def m() -> int { return 0; } } +def main(i: int) -> int { + var e: E = if(i == 0, E.A, if(i == 1, E.B, E.C)); + var f = e.m; + return f(); +} diff --git a/test/enums/enum_cmethod00.v3 b/test/enums/enum_cmethod00.v3 new file mode 100644 index 000000000..b3f2459b4 --- /dev/null +++ b/test/enums/enum_cmethod00.v3 @@ -0,0 +1,11 @@ +//@execute 0=1; 1=0; 2=0 +// Per-case override returns different value +enum E { A { def m() -> int { return 1; } }, B, C; def m() -> int { return 0; } } +def main(i: int) -> int { + match (i) { + 0 => return E.A.m(); + 1 => return E.B.m(); + 2 => return E.C.m(); + } + return -1; +} diff --git a/test/enums/enum_cmethod01.v3 b/test/enums/enum_cmethod01.v3 new file mode 100644 index 000000000..e12df79bc --- /dev/null +++ b/test/enums/enum_cmethod01.v3 @@ -0,0 +1,14 @@ +//@execute 0=1; 1=0; 2=2 +// Virtual dispatch through variable +enum E { A { def m() -> int { return 1; } }, B, C { def m() -> int { return 2; } }; def m() -> int { return 0; } } +def run(e: E) -> int { + return e.m(); +} +def main(i: int) -> int { + match (i) { + 0 => return run(E.A); + 1 => return run(E.B); + 2 => return run(E.C); + } + return -1; +} diff --git a/test/enums/enum_cmethod02.v3 b/test/enums/enum_cmethod02.v3 new file mode 100644 index 000000000..801a124c6 --- /dev/null +++ b/test/enums/enum_cmethod02.v3 @@ -0,0 +1,18 @@ +//@execute 0=10; 1=20; 2=42; 3=7 +// Multiple methods, some overridden +enum E { + A { def m() -> int { return 10; } }, + B { def n() -> int { return 20; } }, + C; + def m() -> int { return 42; } + def n() -> int { return 7; } +} +def main(i: int) -> int { + match (i) { + 0 => return E.A.m(); + 1 => return E.B.n(); + 2 => return E.C.m(); + 3 => return E.C.n(); + } + return -1; +} diff --git a/test/enums/enum_cmethod03.v3 b/test/enums/enum_cmethod03.v3 new file mode 100644 index 000000000..301d659f8 --- /dev/null +++ b/test/enums/enum_cmethod03.v3 @@ -0,0 +1,14 @@ +//@execute 0=11; 1=20 +// Override accesses enum fields +enum E(x: int) { + A(10) { def m() -> int { return x + 1; } }, + B(20); + def m() -> int { return x; } +} +def main(i: int) -> int { + match (i) { + 0 => return E.A.m(); + 1 => return E.B.m(); + } + return -1; +} diff --git a/test/enums/enum_cmethod04.v3 b/test/enums/enum_cmethod04.v3 new file mode 100644 index 000000000..faeea502b --- /dev/null +++ b/test/enums/enum_cmethod04.v3 @@ -0,0 +1,18 @@ +//@execute 0=10; 1=0; 2=20; 3=0 +// Virtual dispatch with multiple methods +enum E { + A { def m() -> int { return 10; } }, + B, + C { def m() -> int { return 20; } }; + def m() -> int { return 0; } +} +def callM(e: E) -> int { return e.m(); } +def main(i: int) -> int { + match (i) { + 0 => return callM(E.A); + 1 => return callM(E.B); + 2 => return callM(E.C); + 3 => { var e: E = E.B; return e.m(); } + } + return -1; +} diff --git a/test/enums/enum_cmethod05.v3 b/test/enums/enum_cmethod05.v3 new file mode 100644 index 000000000..b11283ad6 --- /dev/null +++ b/test/enums/enum_cmethod05.v3 @@ -0,0 +1,20 @@ +//@execute 0=1; 1=0; 2=2; 3=0; 4=1; 5=0; 6=2 +// Static dispatch: E.A.m() directly calls override (no dispatch table) +enum E { + A { def m() -> int { return 1; } }, + B, + C { def m() -> int { return 2; } }; + def m() -> int { return 0; } +} +def main(i: int) -> int { + match (i) { + 0 => return E.A.m(); + 1 => return E.B.m(); + 2 => return E.C.m(); + 3 => { var e: E = E.B; return e.m(); } + 4 => { var e: E = E.A; return e.m(); } + 5 => { var b = E.B; return b.m(); } + 6 => { var c = E.C; return c.m(); } + } + return -1; +} diff --git a/test/enums/enum_cmethod06.v3 b/test/enums/enum_cmethod06.v3 new file mode 100644 index 000000000..9e6fb7845 --- /dev/null +++ b/test/enums/enum_cmethod06.v3 @@ -0,0 +1,13 @@ +//@execute 0=10; 1=10; 2=10; 3=10 +// Subtype enum cases inherit parent's method +enum E { A, B, _; def m() -> int { return 10; } } +enum E.More { C, D } +def main(i: int) -> int { + match (i) { + 0 => return E.A.m(); + 1 => return E.B.m(); + 2 => return E.More.C.m(); + 3 => return E.More.D.m(); + } + return -1; +} diff --git a/test/enums/enum_cmethod07.v3 b/test/enums/enum_cmethod07.v3 new file mode 100644 index 000000000..82b4003e0 --- /dev/null +++ b/test/enums/enum_cmethod07.v3 @@ -0,0 +1,13 @@ +//@execute 0=0; 1=0; 2=1; 3=1 +// Subtype enum overrides parent method for all its cases +enum E { A, B, _; def m() -> int { return 0; } } +enum E.More { C, D; def m() -> int { return 1; } } +def main(i: int) -> int { + match (i) { + 0 => return E.A.m(); + 1 => return E.B.m(); + 2 => return E.More.C.m(); + 3 => return E.More.D.m(); + } + return -1; +} diff --git a/test/enums/enum_cmethod08.v3 b/test/enums/enum_cmethod08.v3 new file mode 100644 index 000000000..409071444 --- /dev/null +++ b/test/enums/enum_cmethod08.v3 @@ -0,0 +1,13 @@ +//@execute 0=0; 1=0; 2=2; 3=1 +// Case override in subtype + subtype-level override +enum E { A, B, _; def m() -> int { return 0; } } +enum E.More { C { def m() -> int { return 2; } }, D; def m() -> int { return 1; } } +def main(i: int) -> int { + match (i) { + 0 => return E.A.m(); + 1 => return E.B.m(); + 2 => return E.More.C.m(); + 3 => return E.More.D.m(); + } + return -1; +} diff --git a/test/enums/enum_cmethod09.v3 b/test/enums/enum_cmethod09.v3 new file mode 100644 index 000000000..0e883ea7d --- /dev/null +++ b/test/enums/enum_cmethod09.v3 @@ -0,0 +1,14 @@ +//@execute 0=0; 1=0; 2=2; 3=1 +// Virtual dispatch through parent type +enum E { A, B, _; def m() -> int { return 0; } } +enum E.More { C { def m() -> int { return 2; } }, D; def m() -> int { return 1; } } +def call(e: E) -> int { return e.m(); } +def main(i: int) -> int { + match (i) { + 0 => return call(E.A); + 1 => return call(E.B); + 2 => return call(E.More.C); + 3 => return call(E.More.D); + } + return -1; +} diff --git a/test/enums/enum_method00.v3 b/test/enums/enum_method00.v3 new file mode 100644 index 000000000..a11a7c3ce --- /dev/null +++ b/test/enums/enum_method00.v3 @@ -0,0 +1,11 @@ +//@execute 0=42; 1=7; 2=99 +// Basic enum-level methods: all cases share the same implementation +enum E { A, B, C; def m() -> int { return 42; } def n() -> int { return 7; } } +def main(i: int) -> int { + match (i) { + 0 => return E.A.m(); + 1 => return E.B.n(); + 2 => { var e: E = E.C; return e.m() + e.n() + 50; } + } + return -1; +} diff --git a/test/enums/enum_method01.v3 b/test/enums/enum_method01.v3 new file mode 100644 index 000000000..93c89fcf3 --- /dev/null +++ b/test/enums/enum_method01.v3 @@ -0,0 +1,11 @@ +//@execute 0=1; 1=17; 2=3 +// Enum method accessing fields +enum E(x: int) { A(1), B(17), C(3); def getX() -> int { return x; } } +def main(i: int) -> int { + match (i) { + 0 => return E.A.getX(); + 1 => return E.B.getX(); + 2 => { var e: E = E.C; return e.getX(); } + } + return -1; +} diff --git a/test/enums/enum_method02.v3 b/test/enums/enum_method02.v3 new file mode 100644 index 000000000..eac0eb445 --- /dev/null +++ b/test/enums/enum_method02.v3 @@ -0,0 +1,11 @@ +//@execute 0=0; 1=1; 2=2 +// Enum method using tag field +enum E { A, B, C; def id() -> int { return tag; } } +def main(i: int) -> int { + match (i) { + 0 => return E.A.id(); + 1 => return E.B.id(); + 2 => return E.C.id(); + } + return -1; +} diff --git a/test/enums/enum_method03.v3 b/test/enums/enum_method03.v3 new file mode 100644 index 000000000..403d9a627 --- /dev/null +++ b/test/enums/enum_method03.v3 @@ -0,0 +1,11 @@ +//@execute 0=10; 1=20; 2=30 +// Enum method with parameters +enum E { A, B, C; def mul(x: int) -> int { return (tag + 1) * x; } } +def main(i: int) -> int { + match (i) { + 0 => return E.A.mul(10); + 1 => return E.B.mul(10); + 2 => return E.C.mul(10); + } + return -1; +} diff --git a/test/enums/enum_method04.v3 b/test/enums/enum_method04.v3 new file mode 100644 index 000000000..929e5d1aa --- /dev/null +++ b/test/enums/enum_method04.v3 @@ -0,0 +1,12 @@ +//@execute 0=42; 1=42; 2=42 +// Open enum: subtypes inherit root method +enum E { A, B, _; def m() -> int { return 42; } } +enum E.More { C, D } +def main(i: int) -> int { + match (i) { + 0 => return E.A.m(); + 1 => return E.More.C.m(); + 2 => { var e: E = E.More.D; return e.m(); } + } + return -1; +} diff --git a/test/enums/enum_method05.v3 b/test/enums/enum_method05.v3 new file mode 100644 index 000000000..f456c0176 --- /dev/null +++ b/test/enums/enum_method05.v3 @@ -0,0 +1,13 @@ +//@execute 0=5; 1=99 +// Enum with private method +enum E { A, B; + def getVal() -> int { return helper(); } + private def helper() -> int { return tag + 5; } +} +def main(i: int) -> int { + match (i) { + 0 => return E.A.getVal(); + 1 => { var e: E = E.B; return e.getVal() + 93; } + } + return -1; +} diff --git a/test/enums/enum_method06.v3 b/test/enums/enum_method06.v3 new file mode 100644 index 000000000..a6cb43c5a --- /dev/null +++ b/test/enums/enum_method06.v3 @@ -0,0 +1,11 @@ +//@execute 0=11; 1=22; 2=33 +// Enum method with multiple fields and expression body +enum E(x: int, y: int) { A(1, 10), B(2, 20), C(3, 30); def sum() => x + y; } +def main(i: int) -> int { + match (i) { + 0 => return E.A.sum(); + 1 => return E.B.sum(); + 2 => return E.C.sum(); + } + return -1; +} diff --git a/test/enums/open_enum00.v3 b/test/enums/open_enum00.v3 new file mode 100644 index 000000000..631c33a85 --- /dev/null +++ b/test/enums/open_enum00.v3 @@ -0,0 +1,14 @@ +//@execute 0=1; 1=2; 2=0; 3=0 +// Open enum: basic match with default arm catches subtype cases +enum E { A, B, _ } +enum E.More { C, D } +def main(x: int) -> int { + var cases: Array = [E.A, E.B, E.More.C, E.More.D]; + if (x < 0 || x >= cases.length) return 0; + var e: E = cases[x]; + match (e) { + A => return 1; + B => return 2; + _ => return 0; + } +} diff --git a/test/enums/open_enum01.v3 b/test/enums/open_enum01.v3 new file mode 100644 index 000000000..21f23440d --- /dev/null +++ b/test/enums/open_enum01.v3 @@ -0,0 +1,17 @@ +//@execute 0=1; 1=2; 2=3; 3=3; 4=0 +// Subtype enum: match on subtype name in parent match +enum E1 { A, B, _ } +enum E1.More { C, D } +def dispatch(e: E1) -> int { + match (e) { + A => return 1; + B => return 2; + More => return 3; // range test: is e in More's tag range? + _ => return 0; + } +} +def main(x: int) -> int { + var cases: Array = [E1.A, E1.B, E1.More.C, E1.More.D]; + if (x < 0 || x >= cases.length) return 0; + return dispatch(cases[x]); +} diff --git a/test/enums/open_enum02.v3 b/test/enums/open_enum02.v3 new file mode 100644 index 000000000..f3ffcb455 --- /dev/null +++ b/test/enums/open_enum02.v3 @@ -0,0 +1,10 @@ +//@execute 0=0; 1=0; 2=1; 3=1; 4=0 +// Subtype enum: type query E1.More.?(e) +enum E1 { A, B, _ } +enum E1.More { C, D } +def main(x: int) -> int { + var cases: Array = [E1.A, E1.B, E1.More.C, E1.More.D]; + if (x < 0 || x >= cases.length) return 0; + var e = cases[x]; + return if(E1.More.?(e), 1, 0); +} diff --git a/test/enums/open_enum03.v3 b/test/enums/open_enum03.v3 new file mode 100644 index 000000000..08fcdf577 --- /dev/null +++ b/test/enums/open_enum03.v3 @@ -0,0 +1,11 @@ +//@execute 0=65; 1=66; 2=77; 3=77; 4=0 +// Subtype enum: .name returns qualified names (e.g. "More.C" for subtype cases) +enum E1 { A, B, _ } +enum E1.More { C, D } +def main(x: int) -> int { + var cases: Array = [E1.A, E1.B, E1.More.C, E1.More.D]; + if (x < 0 || x >= cases.length) return 0; + var e = cases[x]; + var n = e.name; // "A", "B", "More.C", "More.D" + return if(n.length > 0, n[0], 0); +} diff --git a/test/enums/open_enum04.v3 b/test/enums/open_enum04.v3 new file mode 100644 index 000000000..4536a9b3c --- /dev/null +++ b/test/enums/open_enum04.v3 @@ -0,0 +1,11 @@ +//@execute 0=65; 1=66; 2=67; 3=68; 4=0 +// Subtype enum: .shortName returns unqualified case names +enum E1 { A, B, _ } +enum E1.More { C, D } +def main(x: int) -> int { + var cases: Array = [E1.A, E1.B, E1.More.C, E1.More.D]; + if (x < 0 || x >= cases.length) return 0; + var e = cases[x]; + var n = e.shortName; // "A", "B", "C", "D" + return if(n.length > 0, n[0], 0); +} diff --git a/test/enums/open_enum05.v3 b/test/enums/open_enum05.v3 new file mode 100644 index 000000000..175645ec6 --- /dev/null +++ b/test/enums/open_enum05.v3 @@ -0,0 +1,9 @@ +//@execute 0=0; 1=1; 2=2; 3=3; 4=0 +// Subtype enum: .tag returns DFS-assigned global tag +enum E1 { A, B, _ } +enum E1.More { C, D } +def main(x: int) -> int { + var cases: Array = [E1.A, E1.B, E1.More.C, E1.More.D]; + if (x < 0 || x >= cases.length) return 0; + return cases[x].tag; // 0, 1, 2, 3 +} diff --git a/test/enums/open_enum06.v3 b/test/enums/open_enum06.v3 new file mode 100644 index 000000000..02fae9b3b --- /dev/null +++ b/test/enums/open_enum06.v3 @@ -0,0 +1,10 @@ +//@execute 0=10; 1=20; 2=30; 3=40; 4=0 +// Open enum: subtype cases must provide params when root has params +enum E1(x: int) { A(10), B(20), _ } +enum E1.More { C(30), D(40) } +def main(x: int) -> int { + var cases: Array = [E1.A, E1.B, E1.More.C, E1.More.D]; + if (x < 0 || x >= cases.length) return 0; + var e: E1 = cases[x]; + return e.x; +} diff --git a/test/enums/open_enum07.v3 b/test/enums/open_enum07.v3 new file mode 100644 index 000000000..f8d364527 --- /dev/null +++ b/test/enums/open_enum07.v3 @@ -0,0 +1,16 @@ +//@execute 0=1; 1=2; 2=10; 3=10; 4=20; 5=0 +// Open enum: multiple sibling subtype enums, match arm for each +enum E { A, B, _ } +enum E.Nums { N1, N2 } +enum E.Tens { T1 } +def main(x: int) -> int { + var cases: Array = [E.A, E.B, E.Nums.N1, E.Nums.N2, E.Tens.T1]; + if (x < 0 || x >= cases.length) return 0; + match (cases[x]) { + A => return 1; + B => return 2; + Nums => return 10; + Tens => return 20; + _ => return 0; + } +} diff --git a/test/enums/open_enum08.v3 b/test/enums/open_enum08.v3 new file mode 100644 index 000000000..c09b5feee --- /dev/null +++ b/test/enums/open_enum08.v3 @@ -0,0 +1,11 @@ +//@execute 0=2; 1=3; 2=0 +// Open enum: variable declared as subtype enum type, .tag returns DFS global tag +enum E1 { A, B, _ } +enum E1.More { C, D } +def main(x: int) -> int { + var e: E1.More; + if (x == 0) e = E1.More.C; + else if (x == 1) e = E1.More.D; + else return 0; + return e.tag; // C=2, D=3 +} diff --git a/test/enums/open_enum09.v3 b/test/enums/open_enum09.v3 new file mode 100644 index 000000000..8de91c53e --- /dev/null +++ b/test/enums/open_enum09.v3 @@ -0,0 +1,12 @@ +//@execute 0=0; 1=1; 2=1; 3=0 +// Open enum: widening in function argument (E1.More -> E1), type query in called fn +enum E1 { A, _ } +enum E1.More { B, C } +def isMore(e: E1) -> int { + return if(E1.More.?(e), 1, 0); +} +def main(x: int) -> int { + var cases: Array = [E1.A, E1.More.B, E1.More.C]; + if (x < 0 || x >= cases.length) return 0; + return isMore(cases[x]); +} diff --git a/test/enums/open_enum10.v3 b/test/enums/open_enum10.v3 new file mode 100644 index 000000000..997967542 --- /dev/null +++ b/test/enums/open_enum10.v3 @@ -0,0 +1,19 @@ +//@execute 0=1; 1=2; 2=10; 3=20; 4=0 +// Open enum: multi-level hierarchy (E.More has _ allowing E.More.Extra) +// DFS tags: A=0, B=1, More.C=2, More.Extra.D=3 +enum E { A, B, _ } +enum E.More { C, _ } +enum E.More.Extra { D } +def main(x: int) -> int { + var cases: Array = [E.A, E.B, E.More.C, E.More.Extra.D]; + if (x < 0 || x >= cases.length) return 0; + match (cases[x]) { + A => return 1; + B => return 2; + More => { + if (E.More.Extra.?(cases[x])) return 20; // More.Extra subrange + return 10; // More.C (in More but not Extra) + } + _ => return 0; + } +} diff --git a/test/enums/open_field00.v3 b/test/enums/open_field00.v3 new file mode 100644 index 000000000..c4dfd5ac6 --- /dev/null +++ b/test/enums/open_field00.v3 @@ -0,0 +1,9 @@ +//@execute 0=1; 1=17; 2=10; 3=20 +// Form 1: explicit redecl — subtype cases provide field values +enum E(x: int) { A(1), B(17), _ } +enum E.S1(x: int) { C(10), D(20) } +def main(i: int) -> int { + var cases: Array = [E.A, E.B, E.S1.C, E.S1.D]; + if (i < 0 || i >= cases.length) return -1; + return cases[i].x; +} diff --git a/test/enums/open_field01.v3 b/test/enums/open_field01.v3 new file mode 100644 index 000000000..72e02443e --- /dev/null +++ b/test/enums/open_field01.v3 @@ -0,0 +1,9 @@ +//@execute 0=1; 1=17; 2=15 +// Form 2: (super) keyword — subtype cases provide field values +enum E(x: int) { A(1), B(17), _ } +enum E.S2(super) { D(15) } +def main(i: int) -> int { + var cases: Array = [E.A, E.B, E.S2.D]; + if (i < 0 || i >= cases.length) return -1; + return cases[i].x; +} diff --git a/test/enums/open_field02.v3 b/test/enums/open_field02.v3 new file mode 100644 index 000000000..789514e4f --- /dev/null +++ b/test/enums/open_field02.v3 @@ -0,0 +1,9 @@ +//@execute 0=1; 1=17; 2=23 +// Form 3: implicit — subtype has no param decl, inherits parent's +enum E(x: int) { A(1), B(17), _ } +enum E.S3 { H(23) } +def main(i: int) -> int { + var cases: Array = [E.A, E.B, E.S3.H]; + if (i < 0 || i >= cases.length) return -1; + return cases[i].x; +} diff --git a/test/enums/open_field03.v3 b/test/enums/open_field03.v3 new file mode 100644 index 000000000..fe4a5db4e --- /dev/null +++ b/test/enums/open_field03.v3 @@ -0,0 +1,15 @@ +//@execute 0=11; 1=21; 2=31; 3=12; 4=22; 5=32 +// Multiple params: subtype redeclares, all field accesses correct +enum E(a: int, b: int) { X(1, 2), _ } +enum E.S(a: int, b: int) { Y(10, 20) } +def main(i: int) -> int { + match (i) { + 0 => return E.X.a + 10; + 1 => return E.X.b + 19; + 2 => return E.S.Y.a + 21; + 3 => return E.S.Y.b + -8; + 4 => { var e: E = E.S.Y; return e.a + 12; } + 5 => { var e: E = E.S.Y; return e.b + 12; } + _ => return -1; + } +} diff --git a/test/enums/open_field04.v3 b/test/enums/open_field04.v3 new file mode 100644 index 000000000..8f9a8bf04 --- /dev/null +++ b/test/enums/open_field04.v3 @@ -0,0 +1,11 @@ +//@execute 0=10; 1=20 +// Access field on subtype-typed variable +enum E(x: int) { A(1), _ } +enum E.S1(x: int) { C(10), D(20) } +def main(i: int) -> int { + match (i) { + 0 => { var v: E.S1 = E.S1.C; return v.x; } + 1 => { var v: E.S1 = E.S1.D; return v.x; } + _ => return -1; + } +} diff --git a/test/enums/open_field05.v3 b/test/enums/open_field05.v3 new file mode 100644 index 000000000..1615a28d7 --- /dev/null +++ b/test/enums/open_field05.v3 @@ -0,0 +1,9 @@ +//@execute 0=1; 1=10; 2=20 +// Access field on root-typed variable holding subtype value +enum E(x: int) { A(1), _ } +enum E.S1(x: int) { C(10), D(20) } +def main(i: int) -> int { + var cases: Array = [E.A, E.S1.C, E.S1.D]; + if (i < 0 || i >= cases.length) return -1; + return cases[i].x; +} diff --git a/test/enums/open_field06.v3 b/test/enums/open_field06.v3 new file mode 100644 index 000000000..6cbb5d81b --- /dev/null +++ b/test/enums/open_field06.v3 @@ -0,0 +1,13 @@ +//@execute 0=10; 1=20; 2=100; 3=200 +// (super, extra) — access both inherited and new fields +enum E(x: int) { A(10), B(20), _ } +enum E.S1(super, f: float) { C(100, 1.5f), D(200, 2.5f) } +def main(i: int) -> int { + match (i) { + 0 => return E.A.x; + 1 => return E.B.x; + 2 => return E.S1.C.x; + 3 => return E.S1.D.x; + } + return -1; +} diff --git a/test/enums/open_field07.v3 b/test/enums/open_field07.v3 new file mode 100644 index 000000000..bbb154024 --- /dev/null +++ b/test/enums/open_field07.v3 @@ -0,0 +1,13 @@ +//@execute 0=10; 1=20; 2=100; 3=200 +// Restate + add — access all fields +enum E(x: int) { A(10), B(20), _ } +enum E.S1(x: int, f: float) { C(100, 1.5f), D(200, 2.5f) } +def main(i: int) -> int { + match (i) { + 0 => return E.A.x; + 1 => return E.B.x; + 2 => return E.S1.C.x; + 3 => return E.S1.D.x; + } + return -1; +} diff --git a/test/enums/open_field08.v3 b/test/enums/open_field08.v3 new file mode 100644 index 000000000..17515987c --- /dev/null +++ b/test/enums/open_field08.v3 @@ -0,0 +1,13 @@ +//@execute 0=1; 1=2; 2=150; 3=250 +// Access new field on subtype-typed variable, inherited on root-typed +enum E(x: int) { A(1), B(2), _ } +enum E.S1(super, y: int) { C(10, 150), D(20, 250) } +def main(i: int) -> int { + match (i) { + 0 => return E.A.x; + 1 => return E.B.x; + 2 => { var v: E.S1 = E.S1.C; return v.y; } + 3 => { var v: E.S1 = E.S1.D; return v.y; } + } + return -1; +} diff --git a/test/enums/open_field09.v3 b/test/enums/open_field09.v3 new file mode 100644 index 000000000..53459c445 --- /dev/null +++ b/test/enums/open_field09.v3 @@ -0,0 +1,14 @@ +//@execute 0=10; 1=20; 2=100; 3=200 +// Multiple subtypes with different extra fields +enum E(x: int) { A(10), _ } +enum E.S1(super, y: int) { B(20, 100) } +enum E.S2(super, z: int) { C(30, 200) } +def main(i: int) -> int { + match (i) { + 0 => return E.A.x; + 1 => return E.S1.B.x; + 2 => { var v: E.S1 = E.S1.B; return v.y; } + 3 => { var v: E.S2 = E.S2.C; return v.z; } + } + return -1; +} diff --git a/test/enums/open_field10.v3 b/test/enums/open_field10.v3 new file mode 100644 index 000000000..68cb562bc --- /dev/null +++ b/test/enums/open_field10.v3 @@ -0,0 +1,12 @@ +//@execute 0=10; 1=100; 2=250 +// Form 1+add: access both inherited and new fields +enum E(x: int) { A(10), _ } +enum E.S1(x: int, y: int) { C(100, 150), D(200, 250) } +def main(i: int) -> int { + match (i) { + 0 => return E.A.x; + 1 => { var v: E.S1 = E.S1.C; return v.x; } + 2 => { var v: E.S1 = E.S1.D; return v.y; } + } + return -1; +} diff --git a/test/enums/open_field11.v3 b/test/enums/open_field11.v3 new file mode 100644 index 000000000..c009c7685 --- /dev/null +++ b/test/enums/open_field11.v3 @@ -0,0 +1,13 @@ +//@execute 0=10; 1=20; 2=30; 3=133 +// Multiple extra fields in one subtype +enum E(x: int) { A(10), _ } +enum E.S(super, y: int, z: int) { B(20, 30, 40), C(50, 60, 70) } +def main(i: int) -> int { + match (i) { + 0 => return E.A.x; + 1 => return E.S.B.x; + 2 => { var v: E.S = E.S.B; return v.y; } + 3 => { var v: E.S = E.S.B; return v.y + v.z + E.S.C.x + v.x - 7; } + } + return -1; +} diff --git a/test/enums/open_field12.v3 b/test/enums/open_field12.v3 new file mode 100644 index 000000000..e784d85ea --- /dev/null +++ b/test/enums/open_field12.v3 @@ -0,0 +1,12 @@ +//@execute 0=1; 1=2; 2=0 +// Float extra field round-trip (cast to int to verify value) +enum E(x: int) { A(10), _ } +enum E.S(super, f: float) { B(20, 1.5f), C(30, 2.75f) } +def main(i: int) -> int { + match (i) { + 0 => { var v: E.S = E.S.B; return int.truncf(v.f); } + 1 => { var v: E.S = E.S.C; return int.truncf(v.f); } + 2 => { var v: E.S = E.S.B; return int.truncf(v.f - 1.5f); } + } + return -1; +} diff --git a/test/enums/open_field13.v3 b/test/enums/open_field13.v3 new file mode 100644 index 000000000..d90ec30f2 --- /dev/null +++ b/test/enums/open_field13.v3 @@ -0,0 +1,9 @@ +//@execute 0=10; 1=100; 2=200 +// Root-typed variable holding subtype value (Form 1+add), inherited field access +enum E(x: int) { A(10), _ } +enum E.S(x: int, y: int) { B(100, 150), C(200, 250) } +def main(i: int) -> int { + var cases: Array = [E.A, E.S.B, E.S.C]; + if (i < 0 || i >= cases.length) return -1; + return cases[i].x; +} diff --git a/test/enums/open_field14.v3 b/test/enums/open_field14.v3 new file mode 100644 index 000000000..8a60f94e9 --- /dev/null +++ b/test/enums/open_field14.v3 @@ -0,0 +1,15 @@ +//@execute 0=1; 1=2; 2=3; 3=20; 4=30 +// Multi-level hierarchy: grandchild inherits intermediate extra field +enum E(x: int) { A(1), _ } +enum E.S(super, y: int) { B(2, 20), _ } +enum E.S.T(super, z: int) { C(3, 20, 30) } +def main(i: int) -> int { + match (i) { + 0 => return E.A.x; + 1 => return E.S.B.x; + 2 => return E.S.T.C.x; + 3 => { var v: E.S.T = E.S.T.C; return v.y; } + 4 => { var v: E.S.T = E.S.T.C; return v.z; } + } + return -1; +} diff --git a/test/enums/open_field15.v3 b/test/enums/open_field15.v3 new file mode 100644 index 000000000..f13e03493 --- /dev/null +++ b/test/enums/open_field15.v3 @@ -0,0 +1,15 @@ +//@execute 0=1; 1=2; 2=20; 3=3; 4=25 +// Multi-level (super) only — grandchild provides root + intermediate extras, no own extras +enum E(x: int) { A(1), _ } +enum E.S(super, y: int) { B(2, 20), _ } +enum E.S.T(super) { C(3, 25) } +def main(i: int) -> int { + match (i) { + 0 => return E.A.x; + 1 => return E.S.B.x; + 2 => { var v: E.S = E.S.B; return v.y; } + 3 => return E.S.T.C.x; + 4 => { var v: E.S.T = E.S.T.C; return v.y; } + } + return -1; +} diff --git a/test/enums/open_set00.v3 b/test/enums/open_set00.v3 new file mode 100644 index 000000000..6da51a584 --- /dev/null +++ b/test/enums/open_set00.v3 @@ -0,0 +1,14 @@ +//@execute 0=1; 1=1; 2=0; 3=0 +// Open enum set: subtype cases in set, membership via .C and .D +enum E { A, B, _ } +enum E.More { C, D } +def main(x: int) -> int { + var s: E.set = E.More.C | E.More.D; + match (x) { + 0 => return if(s.C, 1, 0); + 1 => return if(s.D, 1, 0); + 2 => return if(s.A, 1, 0); + 3 => return if(s.B, 1, 0); + } + return -1; +} diff --git a/test/enums/open_set01.v3 b/test/enums/open_set01.v3 new file mode 100644 index 000000000..a245e92f3 --- /dev/null +++ b/test/enums/open_set01.v3 @@ -0,0 +1,22 @@ +//@execute 0=4; 1=2; 2=2; 3=4; 4=0 +// Open enum set: .all covers entire hierarchy; union and intersect +enum E { A, B, _ } +enum E.More { C, D } +def count(s: E.set) -> int { + var n = 0; + for (e in s) n++; + return n; +} +def main(x: int) -> int { + var all = E.set.all; + var ab: E.set = E.A | E.B; + var cd: E.set = E.More.C | E.More.D; + match (x) { + 0 => return count(all); + 1 => return count(ab); + 2 => return count(cd); + 3 => return count(ab | cd); + 4 => return count(ab & cd); + } + return -1; +} diff --git a/test/enums/open_set02.v3 b/test/enums/open_set02.v3 new file mode 100644 index 000000000..be0ec5257 --- /dev/null +++ b/test/enums/open_set02.v3 @@ -0,0 +1,12 @@ +//@execute 0=4; 1=2 +// Open enum set: for-in iteration yields correct count +enum E { A, B, _ } +enum E.More { C, D } +def main(x: int) -> int { + var all = E.set.all; + var cd: E.set = E.More.C | E.More.D; + var s = if(x == 0, all, cd); + var count = 0; + for (e in s) count++; + return count; +} diff --git a/test/enums/open_set03.v3 b/test/enums/open_set03.v3 new file mode 100644 index 000000000..dfbdf2b2a --- /dev/null +++ b/test/enums/open_set03.v3 @@ -0,0 +1,25 @@ +//@execute 0=5; 1=1; 2=1 +// Open enum set: multiple siblings share root set type +enum E { A, _ } +enum E.Nums { N1, N2 } +enum E.Tens { T1, T2 } +def main(x: int) -> int { + // DFS tags: A=0, N1=1, N2=2, T1=3, T2=4 + var all = E.set.all; + match (x) { + 0 => { + var count = 0; + for (e in all) count++; + return count; + } + 1 => { + var s: E.set = E.Nums.N1 | E.Nums.N2; + return if(s.N1, 1, 0); + } + 2 => { + var s: E.set = E.Tens.T1 | E.Tens.T2; + return if(s.T2, 1, 0); + } + } + return -1; +} diff --git a/test/enums/open_set04.v3 b/test/enums/open_set04.v3 new file mode 100644 index 000000000..b9f10ac0a --- /dev/null +++ b/test/enums/open_set04.v3 @@ -0,0 +1,14 @@ +//@execute 0=1; 1=0; 2=1 +// Open enum set: cross-level union E.A | E.More.C promotes to E.set +enum E { A, B, _ } +enum E.More { C, D } +def main(x: int) -> int { + // DFS tags: A=0, B=1, C=2, D=3 + var s: E.set = E.A | E.More.C; + match (x) { + 0 => return if(s.A, 1, 0); + 1 => return if(s.B, 1, 0); + 2 => return if(s.C, 1, 0); + } + return -1; +} diff --git a/test/enums/parser/enum_cmethod00.v3 b/test/enums/parser/enum_cmethod00.v3 new file mode 100644 index 000000000..812a44384 --- /dev/null +++ b/test/enums/parser/enum_cmethod00.v3 @@ -0,0 +1,3 @@ +//@parse +// Enum with per-case method override +enum E { A { def m() -> int { return 1; } }, B; def m() -> int { return 0; } } diff --git a/test/enums/parser/enum_cmethod01.v3 b/test/enums/parser/enum_cmethod01.v3 new file mode 100644 index 000000000..7fdd52ec3 --- /dev/null +++ b/test/enums/parser/enum_cmethod01.v3 @@ -0,0 +1,9 @@ +//@parse +// Multiple cases with overrides +enum E { + A { def m() -> int { return 1; } def n() -> int { return 10; } }, + B { def m() -> int { return 2; } }, + C; + def m() -> int { return 0; } + def n() -> int { return 0; } +} diff --git a/test/enums/parser/enum_cmethod02.v3 b/test/enums/parser/enum_cmethod02.v3 new file mode 100644 index 000000000..399fcf76f --- /dev/null +++ b/test/enums/parser/enum_cmethod02.v3 @@ -0,0 +1,3 @@ +//@parse +// Default case with override +enum E { A, B, _ { def m() => 99; }; def m() => 0; } diff --git a/test/enums/parser/enum_cmethod03.v3 b/test/enums/parser/enum_cmethod03.v3 new file mode 100644 index 000000000..3fcb5136e --- /dev/null +++ b/test/enums/parser/enum_cmethod03.v3 @@ -0,0 +1,3 @@ +//@parse = ParseError +// var not allowed in enum case body +enum E { A { var x: int; }, B; } diff --git a/test/enums/parser/enum_method00.v3 b/test/enums/parser/enum_method00.v3 new file mode 100644 index 000000000..feaf94199 --- /dev/null +++ b/test/enums/parser/enum_method00.v3 @@ -0,0 +1,3 @@ +//@parse +// Enum with a single method +enum E { A, B; def m() -> int { return 0; } } diff --git a/test/enums/parser/enum_method01.v3 b/test/enums/parser/enum_method01.v3 new file mode 100644 index 000000000..46e4afd12 --- /dev/null +++ b/test/enums/parser/enum_method01.v3 @@ -0,0 +1,7 @@ +//@parse +// Enum with multiple methods and expression body +enum E { A, B, C; + def m() => 42; + def n(x: int) -> int { return x + 1; } + private def helper() -> bool { return true; } +} diff --git a/test/enums/parser/enum_method02.v3 b/test/enums/parser/enum_method02.v3 new file mode 100644 index 000000000..bfe6f8895 --- /dev/null +++ b/test/enums/parser/enum_method02.v3 @@ -0,0 +1,3 @@ +//@parse +// Enum with fields and methods +enum E(x: int) { A(1), B(2); def getX() -> int { return x; } } diff --git a/test/enums/parser/enum_method03.v3 b/test/enums/parser/enum_method03.v3 new file mode 100644 index 000000000..d4b6ed82c --- /dev/null +++ b/test/enums/parser/enum_method03.v3 @@ -0,0 +1,4 @@ +//@parse +// Open enum with methods +enum E { A, B, _; def m() => 0; } +enum E.Sub { C, D } diff --git a/test/enums/parser/open00.v3 b/test/enums/parser/open00.v3 new file mode 100644 index 000000000..f8e57e116 --- /dev/null +++ b/test/enums/parser/open00.v3 @@ -0,0 +1,3 @@ +//@parse +// Open enum with just _ case +enum E { A, B, _ } diff --git a/test/enums/parser/open01.v3 b/test/enums/parser/open01.v3 new file mode 100644 index 000000000..249b5048c --- /dev/null +++ b/test/enums/parser/open01.v3 @@ -0,0 +1,4 @@ +//@parse +// Subtype enum declaration +enum E1 { A, B, _ } +enum E1.More { C, D } diff --git a/test/enums/parser/open02.v3 b/test/enums/parser/open02.v3 new file mode 100644 index 000000000..bf4e7ce6c --- /dev/null +++ b/test/enums/parser/open02.v3 @@ -0,0 +1,4 @@ +//@parse +// Subtype enum with params +enum E1(x: int) { A(1), B(2), _ } +enum E1.More(x: int) { C(3), D(4) } diff --git a/test/enums/parser/open03.v3 b/test/enums/parser/open03.v3 new file mode 100644 index 000000000..f425076c9 --- /dev/null +++ b/test/enums/parser/open03.v3 @@ -0,0 +1,3 @@ +//@parse = ParseError @ 1:14 +// _ case cannot be followed by more cases +enum E { A, B, _, C } diff --git a/test/enums/seman/enum_cmethod00.v3 b/test/enums/seman/enum_cmethod00.v3 new file mode 100644 index 000000000..8d29c966b --- /dev/null +++ b/test/enums/seman/enum_cmethod00.v3 @@ -0,0 +1,7 @@ +//@seman +// Per-case override type-checks correctly +enum E { A { def m() -> int { return 1; } }, B; def m() -> int { return 0; } } +def main() { + var x: int = E.A.m(); + var y: int = E.B.m(); +} diff --git a/test/enums/seman/enum_cmethod01.v3 b/test/enums/seman/enum_cmethod01.v3 new file mode 100644 index 000000000..55ea405c1 --- /dev/null +++ b/test/enums/seman/enum_cmethod01.v3 @@ -0,0 +1,3 @@ +//@seman = InheritanceError +// Override with mismatched return type +enum E { A { def m() -> byte { return 1; } }, B; def m() -> int { return 0; } } diff --git a/test/enums/seman/enum_cmethod02.v3 b/test/enums/seman/enum_cmethod02.v3 new file mode 100644 index 000000000..c0795aee5 --- /dev/null +++ b/test/enums/seman/enum_cmethod02.v3 @@ -0,0 +1,3 @@ +//@seman = EnumDeclError +// Case method that doesn't override any enum-level method +enum E { A { def m() -> int { return 1; } }, B; } diff --git a/test/enums/seman/enum_cmethod03.v3 b/test/enums/seman/enum_cmethod03.v3 new file mode 100644 index 000000000..4ae2958cb --- /dev/null +++ b/test/enums/seman/enum_cmethod03.v3 @@ -0,0 +1,7 @@ +//@seman +// Override accesses enum fields +enum E(x: int) { A(10) { def m() -> int { return x + 1; } }, B(20); def m() -> int { return x; } } +def main() { + var a: int = E.A.m(); + var b: int = E.B.m(); +} diff --git a/test/enums/seman/enum_method00.v3 b/test/enums/seman/enum_method00.v3 new file mode 100644 index 000000000..af5cd4d83 --- /dev/null +++ b/test/enums/seman/enum_method00.v3 @@ -0,0 +1,7 @@ +//@seman +// Enum methods type-check correctly +enum E { A, B; def m() -> int { return 42; } } +def main() { + var x: int = E.A.m(); + var y: int = E.B.m(); +} diff --git a/test/enums/seman/enum_method01.v3 b/test/enums/seman/enum_method01.v3 new file mode 100644 index 000000000..b793e7a6b --- /dev/null +++ b/test/enums/seman/enum_method01.v3 @@ -0,0 +1,10 @@ +//@seman +// Enum method accessing fields and tag +enum E(x: int) { A(1), B(2); + def getX() -> int { return x; } + def id() -> int { return tag; } +} +def main() { + var a: int = E.A.getX(); + var b: int = E.B.id(); +} diff --git a/test/enums/seman/enum_method02.v3 b/test/enums/seman/enum_method02.v3 new file mode 100644 index 000000000..bc6c3015c --- /dev/null +++ b/test/enums/seman/enum_method02.v3 @@ -0,0 +1,3 @@ +//@seman = TypeError +// Enum method return type mismatch +enum E { A, B; def m() -> int { return true; } } diff --git a/test/enums/seman/enum_method03.v3 b/test/enums/seman/enum_method03.v3 new file mode 100644 index 000000000..ed14e9287 --- /dev/null +++ b/test/enums/seman/enum_method03.v3 @@ -0,0 +1,9 @@ +//@seman +// Private enum method not accessible externally, but callable internally +enum E { A, B; + def pub() -> int { return priv(); } + private def priv() -> int { return 1; } +} +def main() { + var x: int = E.A.pub(); +} diff --git a/test/enums/seman/open00.v3 b/test/enums/seman/open00.v3 new file mode 100644 index 000000000..e72c483be --- /dev/null +++ b/test/enums/seman/open00.v3 @@ -0,0 +1,12 @@ +//@seman +// Open enum: match requires default arm +enum E { A, B, _ } +component C { + def f(e: E) -> int { + match (e) { + A => return 1; + B => return 2; + _ => return 0; + } + } +} diff --git a/test/enums/seman/open01.v3 b/test/enums/seman/open01.v3 new file mode 100644 index 000000000..18480ac54 --- /dev/null +++ b/test/enums/seman/open01.v3 @@ -0,0 +1,11 @@ +//@seman = MatchError @ 3:9 +// Open enum: match without default arm must error +enum E { A, B, _ } +component C { + def f(e: E) -> int { + match (e) { + A => return 1; + B => return 2; + } + } +} diff --git a/test/enums/seman/open02.v3 b/test/enums/seman/open02.v3 new file mode 100644 index 000000000..0333e2dc2 --- /dev/null +++ b/test/enums/seman/open02.v3 @@ -0,0 +1,6 @@ +//@seman +// Open enum: .set type is now allowed +enum E { A, B, _ } +component C { + def f() -> E.set { return E.A; } +} diff --git a/test/enums/seman/open03.v3 b/test/enums/seman/open03.v3 new file mode 100644 index 000000000..1fee28545 --- /dev/null +++ b/test/enums/seman/open03.v3 @@ -0,0 +1,14 @@ +//@seman +// Subtype enum: declare and use +enum E1 { A, B, _ } +enum E1.More { C, D } +component C { + def f(e: E1) -> int { + match (e) { + A => return 1; + B => return 2; + More => return 3; + _ => return 0; + } + } +} diff --git a/test/enums/seman/open04.v3 b/test/enums/seman/open04.v3 new file mode 100644 index 000000000..efe5b08e0 --- /dev/null +++ b/test/enums/seman/open04.v3 @@ -0,0 +1,4 @@ +//@seman = EnumDeclError @ 2:6 +// Subtype enum: parent must have _ case +enum E1 { A, B } +enum E1.More { C, D } diff --git a/test/enums/seman/open05.v3 b/test/enums/seman/open05.v3 new file mode 100644 index 000000000..17ca8ead6 --- /dev/null +++ b/test/enums/seman/open05.v3 @@ -0,0 +1,9 @@ +//@seman +// Subtype enum: widening assignment E1.More -> E1 +enum E1 { A, B, _ } +enum E1.More { C, D } +component C { + def f(e: E1.More) -> E1 { + return e; // widening (subsume) + } +} diff --git a/test/enums/seman/open06.v3 b/test/enums/seman/open06.v3 new file mode 100644 index 000000000..b494ce987 --- /dev/null +++ b/test/enums/seman/open06.v3 @@ -0,0 +1,9 @@ +//@seman +// Subtype enum: type query E1.More.?(x) +enum E1 { A, B, _ } +enum E1.More { C, D } +component C { + def f(e: E1) -> bool { + return E1.More.?(e); + } +} diff --git a/test/enums/seman/open07.v3 b/test/enums/seman/open07.v3 new file mode 100644 index 000000000..4166f87c8 --- /dev/null +++ b/test/enums/seman/open07.v3 @@ -0,0 +1,8 @@ +//@seman +// Subtype enum: multi-level hierarchy (E.More has _ case allowing E.More.Extra) +enum E { A, _ } +enum E.More { B, _ } +enum E.More.Extra { C } +component C { + def f(e: E) -> bool { return E.More.Extra.?(e); } +} diff --git a/test/enums/seman/open08.v3 b/test/enums/seman/open08.v3 new file mode 100644 index 000000000..62aaa7baf --- /dev/null +++ b/test/enums/seman/open08.v3 @@ -0,0 +1,5 @@ +//@seman = EnumDeclError @ 3:6 +// Subtype enum: intermediate level has no _ case, subtype of it must fail +enum E { A, _ } +enum E.More { B } +enum E.More.Sub { C } diff --git a/test/enums/seman/open09.v3 b/test/enums/seman/open09.v3 new file mode 100644 index 000000000..8dbd994b9 --- /dev/null +++ b/test/enums/seman/open09.v3 @@ -0,0 +1,4 @@ +//@seman = EnumDeclError @ 3:6 +// Subtype enum: name conflicts with named case of parent +enum E { A, B, _ } +enum E.A { C } diff --git a/test/enums/seman/open10.v3 b/test/enums/seman/open10.v3 new file mode 100644 index 000000000..7dec8f706 --- /dev/null +++ b/test/enums/seman/open10.v3 @@ -0,0 +1,12 @@ +//@seman +// Subtype enum: match on closed subtype-typed variable is exhaustive without _ arm +enum E1 { A, B, _ } +enum E1.More { C, D } +component C { + def f(e: E1.More) -> int { + match (e) { + C => return 1; + D => return 2; + } + } +} diff --git a/test/enums/seman/open_field00.v3 b/test/enums/seman/open_field00.v3 new file mode 100644 index 000000000..267b0b360 --- /dev/null +++ b/test/enums/seman/open_field00.v3 @@ -0,0 +1,9 @@ +//@seman +// Form 1: explicit redecl — params match parent +enum E(x: int) { A(1), B(2), _ } +enum E.S1(x: int) { C(10), D(20) } +component C { + def f(e: E) -> int { + return e.x; + } +} diff --git a/test/enums/seman/open_field01.v3 b/test/enums/seman/open_field01.v3 new file mode 100644 index 000000000..7ff543996 --- /dev/null +++ b/test/enums/seman/open_field01.v3 @@ -0,0 +1,4 @@ +//@seman = EnumDeclError @ 3:1 +// Form 1 mismatch: wrong type +enum E(x: int) { A(1), _ } +enum E.S1(x: bool) { C(true) } diff --git a/test/enums/seman/open_field02.v3 b/test/enums/seman/open_field02.v3 new file mode 100644 index 000000000..4f8da238d --- /dev/null +++ b/test/enums/seman/open_field02.v3 @@ -0,0 +1,4 @@ +//@seman = EnumDeclError @ 3:1 +// Form 1 mismatch: wrong name +enum E(x: int) { A(1), _ } +enum E.S1(y: int) { C(10) } diff --git a/test/enums/seman/open_field03.v3 b/test/enums/seman/open_field03.v3 new file mode 100644 index 000000000..39b348c04 --- /dev/null +++ b/test/enums/seman/open_field03.v3 @@ -0,0 +1,4 @@ +//@seman +// Form 1+add: restate root params + add new field (valid) +enum E(x: int) { A(1), _ } +enum E.S1(x: int, y: int) { C(10, 20) } diff --git a/test/enums/seman/open_field04.v3 b/test/enums/seman/open_field04.v3 new file mode 100644 index 000000000..e1ed7ddaf --- /dev/null +++ b/test/enums/seman/open_field04.v3 @@ -0,0 +1,4 @@ +//@seman = EnumDeclError @ 3:1 +// (super) on parent with no params +enum E { A, B, _ } +enum E.S1(super) { C } diff --git a/test/enums/seman/open_field05.v3 b/test/enums/seman/open_field05.v3 new file mode 100644 index 000000000..e378aca2e --- /dev/null +++ b/test/enums/seman/open_field05.v3 @@ -0,0 +1,4 @@ +//@seman = EnumDeclError @ 3:1 +// Subtype declares params but parent has none +enum E { A, B, _ } +enum E.S1(x: int) { C(10) } diff --git a/test/enums/seman/open_field06.v3 b/test/enums/seman/open_field06.v3 new file mode 100644 index 000000000..c6d37fc8f --- /dev/null +++ b/test/enums/seman/open_field06.v3 @@ -0,0 +1,4 @@ +//@seman +// (super, extra) form — super + add new field +enum E(b: bool, i: int) { A(true, 0), B(false, 1), _ } +enum E.S1(super, f: float) { F(false, 2, 1.0f) } diff --git a/test/enums/seman/open_field07.v3 b/test/enums/seman/open_field07.v3 new file mode 100644 index 000000000..4578f3b4c --- /dev/null +++ b/test/enums/seman/open_field07.v3 @@ -0,0 +1,4 @@ +//@seman +// Restate root params + add new field +enum E(b: bool, i: int) { A(true, 0), B(false, 1), _ } +enum E.S1(b: bool, i: int, f: float) { F(false, 2, 1.0f) } diff --git a/test/enums/seman/open_field08.v3 b/test/enums/seman/open_field08.v3 new file mode 100644 index 000000000..d16a0b5cc --- /dev/null +++ b/test/enums/seman/open_field08.v3 @@ -0,0 +1,4 @@ +//@seman = EnumDeclError @ 4:1 +// New params without restating or using super — error +enum E(b: bool, i: int) { A(true, 0), B(false, 1), _ } +enum E.S1(f: float) { F(1.0f) } diff --git a/test/enums/seman/open_field09.v3 b/test/enums/seman/open_field09.v3 new file mode 100644 index 000000000..6fb52f9d0 --- /dev/null +++ b/test/enums/seman/open_field09.v3 @@ -0,0 +1,4 @@ +//@seman = EnumDeclError @ 3:1 +// (super, extra) on root with no params — error +enum E { A, B, _ } +enum E.S1(super, f: float) { F(1.0f) } diff --git a/test/enums/seman/open_field10.v3 b/test/enums/seman/open_field10.v3 new file mode 100644 index 000000000..024d31225 --- /dev/null +++ b/test/enums/seman/open_field10.v3 @@ -0,0 +1,5 @@ +//@seman = UnresolvedMember @ 5:16 +// Accessing extra field on root-typed variable should fail +enum E(x: int) { A(1), _ } +enum E.S(super, y: int) { B(2, 20) } +def f(v: E) -> int { return v.y; } diff --git a/test/enums/seman/open_field11.v3 b/test/enums/seman/open_field11.v3 new file mode 100644 index 000000000..c77ea9042 --- /dev/null +++ b/test/enums/seman/open_field11.v3 @@ -0,0 +1,4 @@ +//@seman = EnumDeclError @ 4:1 +// Form 1+add with mismatched prefix: first param name doesn't match root +enum E(x: int) { A(1), _ } +enum E.S(y: int, f: float) { C(1, 2.0f) } diff --git a/test/enums/seman/open_field12.v3 b/test/enums/seman/open_field12.v3 new file mode 100644 index 000000000..ce57dad59 --- /dev/null +++ b/test/enums/seman/open_field12.v3 @@ -0,0 +1,5 @@ +//@seman +// Multi-level super-only passes verification +enum E(x: int) { A(1), _ } +enum E.S(super, y: int) { B(2, 20), _ } +enum E.S.T(super) { C(3, 25) } diff --git a/test/open_types/name0.v3 b/test/open_types/name0.v3 new file mode 100644 index 000000000..ab5cd317c --- /dev/null +++ b/test/open_types/name0.v3 @@ -0,0 +1,19 @@ +//@execute 0=1; 1=11; 2=14; 3=15 +// Check length of name +type A { + case X; + case _; +} +type A.BB { + case Y; +} +type A.CCC { + case Z; +} +def main(arg: int) -> int { + var a = A.BB.Y; + if (arg == 1) return 10 + A.X.name.length; + if (arg == 2) return 10 + A.BB.Y.name.length; + if (arg == 3) return 10 + A.CCC.Z.name.length; + return 1; +} diff --git a/test/open_types/name1.v3 b/test/open_types/name1.v3 new file mode 100644 index 000000000..5b273dbc0 --- /dev/null +++ b/test/open_types/name1.v3 @@ -0,0 +1,19 @@ +//@execute 0=1; 1=11; 2=11; 3=12 +// Check length of shortName +type A { + case X; + case _; +} +type A.BB { + case Y; +} +type A.CCC { + case ZZ; +} +def main(arg: int) -> int { + var a = A.BB.Y; + if (arg == 1) return 10 + A.X.shortName.length; + if (arg == 2) return 10 + A.BB.Y.shortName.length; + if (arg == 3) return 10 + A.CCC.ZZ.shortName.length; + return 1; +} diff --git a/test/open_types/name2.v3 b/test/open_types/name2.v3 new file mode 100644 index 000000000..2b8e320d8 --- /dev/null +++ b/test/open_types/name2.v3 @@ -0,0 +1,19 @@ +//@execute 0=11; 1=11; 2=14; 3=15 +// Check length of name when assigned to variable +type A { + case X; + case _; +} +type A.BB { + case Y; +} +type A.CCC { + case Z; +} +def main(arg: int) -> int { + var a: A; + if (arg == 1) a = A.X; + if (arg == 2) a = A.BB.Y; + if (arg == 3) a = A.CCC.Z; + return 10 + a.name.length; +} diff --git a/test/open_types/name3.v3 b/test/open_types/name3.v3 new file mode 100644 index 000000000..2fb2cea18 --- /dev/null +++ b/test/open_types/name3.v3 @@ -0,0 +1,19 @@ +//@execute 0=11; 1=11; 2=12; 3=13 +// Check length of shortName when assigned to variable +type A { + case X; + case _; +} +type A.BB { + case YY; +} +type A.CCC { + case ZZZ; +} +def main(arg: int) -> int { + var a: A; + if (arg == 1) a = A.X; + if (arg == 2) a = A.BB.YY; + if (arg == 3) a = A.CCC.ZZZ; + return 10 + a.shortName.length; +}