Phase 8: static_assert, comptime, #emit, own keyword, trait objects (&dyn Trait), associated types
This commit is contained in:
@@ -415,7 +415,7 @@ func ReadFile(path: String) -> Result<String, IoError> {
|
||||
|
||||
| Task | Status | Details |
|
||||
|------|--------|---------|
|
||||
| `8.2.1` `own` keyword | 🔄 | Syntax parsed, semantic checking not yet implemented |
|
||||
| `8.2.1` `own` keyword | ✅ | `own T` parsed and resolves to `T`; ready for borrow checker integration |
|
||||
| `8.2.2` `borrow` / `&` | ✅ | `&T` shared reference type checked and enforced |
|
||||
| `8.2.3` `mut` references | ✅ | `&mut T` mutable reference type checked and enforced |
|
||||
| `8.2.4` Lifetime elision | ⏳ | Simple rules for common cases; explicit `'a` for complex |
|
||||
@@ -475,9 +475,9 @@ func Main() -> int {
|
||||
|------|--------|---------|
|
||||
| `8.4.1` `const` functions | ✅ | `const func` evaluated at compile time; supports recursion, if/else, arithmetic |
|
||||
| `8.4.2` `const` variables | ✅ | `const X = expr` — compile-time evaluated; C backend emits `#define` |
|
||||
| `8.4.3` Compile-time blocks | ⏳ | `comptime { ... }` for arbitrary compile-time code |
|
||||
| `8.4.4` Static assertions | ⏳ | `static_assert(cond, msg)` for compile-time checks |
|
||||
| `8.4.5` Generated code | ⏳ | `#emit` for compile-time code generation |
|
||||
| `8.4.3` Compile-time blocks | ✅ | `comptime { ... }` for arbitrary compile-time code |
|
||||
| `8.4.4` Static assertions | ✅ | `static_assert(cond, msg)` for compile-time checks |
|
||||
| `8.4.5` Generated code | ✅ | `#emit` for compile-time code generation |
|
||||
|
||||
```bux
|
||||
const func Factorial(n: int) -> int {
|
||||
@@ -493,9 +493,9 @@ const TABLE_SIZE = Factorial(10); // Computed at compile time
|
||||
| Task | Status | Details |
|
||||
|------|--------|---------|
|
||||
| `8.5.1` Traits | ✅ | `interface` + `extend Type for Interface` |
|
||||
| `8.5.2` Associated types | ⏳ | `type Output` inside trait definitions |
|
||||
| `8.5.2` Associated types | ✅ | `type Output` inside trait definitions; substituted in impl blocks |
|
||||
| `8.5.3` Trait bounds | ✅ | `func Sort<T: Comparable>(arr: &mut Array<T>)` — semantic check at call sites |
|
||||
| `8.5.4` Trait objects | ⏳ | `&dyn Trait` for dynamic dispatch (fat pointer) |
|
||||
| `8.5.4` Trait objects | ✅ | `&dyn Trait` for dynamic dispatch (fat pointer) |
|
||||
| `8.5.5` Blanket impls | ⏳ | `impl<T: Display> Printable for T` |
|
||||
|
||||
### 8.6 — Metaprogramming
|
||||
|
||||
+18
-1
@@ -27,8 +27,10 @@ type
|
||||
tekPath
|
||||
tekSlice
|
||||
tekPointer
|
||||
tekOwn ## own T — owned value (gradual ownership)
|
||||
tekRef ## &T — shared reference (gradual ownership)
|
||||
tekMutRef ## &mut T — mutable reference
|
||||
tekDynRef ## &dyn Trait — trait object (fat pointer)
|
||||
tekTuple
|
||||
tekSelf
|
||||
|
||||
@@ -43,8 +45,10 @@ type
|
||||
of tekSlice:
|
||||
sliceElement*: TypeExpr
|
||||
sliceSize*: Expr ## nil for unsized slices T[]
|
||||
of tekPointer, tekRef, tekMutRef:
|
||||
of tekOwn, tekPointer, tekRef, tekMutRef:
|
||||
pointerPointee*: TypeExpr
|
||||
of tekDynRef:
|
||||
dynInterface*: string
|
||||
of tekTuple:
|
||||
tupleElements*: seq[TypeExpr]
|
||||
of tekSelf:
|
||||
@@ -224,6 +228,9 @@ type
|
||||
skReturn
|
||||
skBreak
|
||||
skContinue
|
||||
skStaticAssert
|
||||
skComptime
|
||||
skEmit
|
||||
skDecl
|
||||
|
||||
ElseIf* = object
|
||||
@@ -276,6 +283,14 @@ type
|
||||
stmtBreakLabel*: string
|
||||
of skContinue:
|
||||
stmtContinueLabel*: string
|
||||
of skStaticAssert:
|
||||
stmtStaticAssertCond*: Expr
|
||||
stmtStaticAssertMsg*: Expr
|
||||
of skComptime:
|
||||
stmtComptimeBlock*: Block
|
||||
of skEmit:
|
||||
stmtEmitExpr*: Expr
|
||||
stmtEmitEvaluated*: string ## filled by sema CTFE
|
||||
of skDecl:
|
||||
stmtDecl*: Decl
|
||||
|
||||
@@ -357,11 +372,13 @@ type
|
||||
declUnionFields*: seq[UnionField]
|
||||
of dkInterface:
|
||||
declInterfaceName*: string
|
||||
declInterfaceAssocTypes*: seq[string] ## associated type names: type Output;
|
||||
declInterfaceMethods*: seq[Decl] ## FuncDecl signatures only
|
||||
of dkImpl:
|
||||
declImplTypeName*: string
|
||||
declImplTypeParams*: seq[TypeParam] ## type parameters for generic impl: extend Box<T>
|
||||
declImplInterface*: string ## empty if not for interface
|
||||
declImplAssocTypes*: seq[tuple[name: string, typ: TypeExpr]] ## type Output = int;
|
||||
declImplMethods*: seq[Decl]
|
||||
of dkModule:
|
||||
declModuleName*: string
|
||||
|
||||
+102
-8
@@ -8,6 +8,19 @@ type
|
||||
varCounter*: int
|
||||
declaredVars*: seq[string]
|
||||
|
||||
proc cEscape(s: string): string =
|
||||
## Escape a string for use as a C string literal.
|
||||
result = ""
|
||||
for c in s:
|
||||
case c
|
||||
of '\\': result.add("\\\\")
|
||||
of '"': result.add("\\\"")
|
||||
of '\n': result.add("\\n")
|
||||
of '\r': result.add("\\r")
|
||||
of '\t': result.add("\\t")
|
||||
of '\0': result.add("\\0")
|
||||
else: result.add(c)
|
||||
|
||||
proc initCBackend*(): CBackend =
|
||||
result.output = ""
|
||||
result.indent = 0
|
||||
@@ -60,6 +73,8 @@ proc typeToC*(typ: Type): string =
|
||||
if typ.inner.len > 0:
|
||||
return typeToC(typ.inner[0]) & "*"
|
||||
return "void*"
|
||||
of tkDynRef:
|
||||
return typ.name & "_FatPtr"
|
||||
of tkSlice:
|
||||
if typ.inner.len > 0:
|
||||
return typeToC(typ.inner[0]) & "*"
|
||||
@@ -137,13 +152,23 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
|
||||
else: return "false"
|
||||
of tkStringLiteral:
|
||||
var text = node.litToken.text
|
||||
# Strip c8" c16" c32" prefixes — in C they are just regular string literals
|
||||
if text.startsWith("c32\""):
|
||||
text = text[3..^1]
|
||||
elif text.startsWith("c16\""):
|
||||
text = text[3..^1]
|
||||
elif text.startsWith("c8\""):
|
||||
text = text[2..^1]
|
||||
# If text has no surrounding quotes, it's from constFoldConstDecl (already unescaped)
|
||||
if text.len >= 2 and text[0] == '"' and text[text.len-1] == '"':
|
||||
# Strip c8" c16" c32" prefixes — in C they are just regular string literals
|
||||
if text.startsWith("c32\""):
|
||||
text = "\"" & cEscape(text[4 ..< text.len-1]) & "\""
|
||||
elif text.startsWith("c16\""):
|
||||
text = "\"" & cEscape(text[4 ..< text.len-1]) & "\""
|
||||
elif text.startsWith("c8\""):
|
||||
text = "\"" & cEscape(text[3 ..< text.len-1]) & "\""
|
||||
else:
|
||||
text = "\"" & cEscape(text[1 ..< text.len-1]) & "\""
|
||||
elif text.len >= 2 and text[0] == '"':
|
||||
# Partial quote — escape anyway
|
||||
text = "\"" & cEscape(text[1 ..< text.len]) & "\""
|
||||
else:
|
||||
# No quotes — from constFoldConstDecl, needs wrapping and escaping
|
||||
text = "\"" & cEscape(text) & "\""
|
||||
return text
|
||||
of tkNull:
|
||||
return "NULL"
|
||||
@@ -262,6 +287,22 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
|
||||
else:
|
||||
return &"bux_async_spawn({node.spawnCallee})"
|
||||
|
||||
of hDynRef:
|
||||
let data = be.emitExpr(node.dynRefData)
|
||||
let iface = node.dynRefInterface
|
||||
let concrete = node.dynRefConcreteType
|
||||
return &"({iface}_FatPtr){{.data = {data}, .vtable = &{concrete}_{iface}_VTable}}"
|
||||
|
||||
of hDynCall:
|
||||
let receiver = be.emitExpr(node.dynCallReceiver)
|
||||
let methodName = node.dynCallMethod
|
||||
var args: seq[string] = @[]
|
||||
args.add(&"{receiver}.data")
|
||||
for i in 1 ..< node.dynCallArgs.len:
|
||||
args.add(be.emitExpr(node.dynCallArgs[i]))
|
||||
let argsStr = args.join(", ")
|
||||
return &"({receiver}.vtable->{methodName}({argsStr}))"
|
||||
|
||||
of hIf:
|
||||
# Ternary expression
|
||||
let cond = be.emitExpr(node.ifCond)
|
||||
@@ -333,6 +374,9 @@ proc emitStmt(be: var CBackend, node: HirNode) =
|
||||
of hContinue:
|
||||
be.emitLine("continue;")
|
||||
|
||||
of hEmit:
|
||||
be.emitLine(node.emitCode)
|
||||
|
||||
of hBlock:
|
||||
if node.isScope:
|
||||
be.emitLine("{")
|
||||
@@ -497,6 +541,12 @@ proc emitModule*(be: var CBackend, module: HirModule): string =
|
||||
be.emitLine(&"typedef struct {s.name} {s.name};")
|
||||
if module.structs.len > 0:
|
||||
be.emitLine("")
|
||||
# Forward declarations for trait object fat pointers
|
||||
for iface in module.interfaces:
|
||||
if not iface.hasAssocTypes:
|
||||
be.emitLine(&"typedef struct {iface.name}_FatPtr {iface.name}_FatPtr;")
|
||||
if module.interfaces.len > 0:
|
||||
be.emitLine("")
|
||||
|
||||
# Extern function declarations
|
||||
if module.externFuncs.len > 0:
|
||||
@@ -516,7 +566,7 @@ proc emitModule*(be: var CBackend, module: HirModule): string =
|
||||
of tkIntLiteral:
|
||||
be.emitLine(&"#define {c.name} {tok.text}")
|
||||
of tkStringLiteral:
|
||||
be.emitLine(&"#define {c.name} \"{tok.text}\"")
|
||||
be.emitLine(&"#define {c.name} \"{cEscape(tok.text)}\"")
|
||||
of tkBoolLiteral:
|
||||
be.emitLine(&"#define {c.name} {tok.text}")
|
||||
else:
|
||||
@@ -544,6 +594,50 @@ proc emitModule*(be: var CBackend, module: HirModule): string =
|
||||
be.emitLine(retType & " " & f.name & "(" & params.join(", ") & ");")
|
||||
be.emitLine("")
|
||||
|
||||
# Trait object vtable and fat pointer struct definitions
|
||||
for iface in module.interfaces:
|
||||
if iface.hasAssocTypes:
|
||||
continue # Skip vtables for interfaces with associated types (not yet supported)
|
||||
let ifaceName = iface.name
|
||||
# VTable struct
|
||||
be.emitLine(&"typedef struct {ifaceName}_VTable {{")
|
||||
inc be.indent
|
||||
for m in iface.methods:
|
||||
var paramCtypes: seq[string] = @[]
|
||||
for i, p in m.params:
|
||||
if i == 0:
|
||||
paramCtypes.add("void* self") # First param is always self (erased)
|
||||
else:
|
||||
paramCtypes.add(typeToC(p) & " param")
|
||||
if paramCtypes.len == 0:
|
||||
paramCtypes.add("void")
|
||||
let ret = typeToC(m.ret)
|
||||
let paramsStr = paramCtypes.join(", ")
|
||||
be.emitLine(&"{ret} (*{m.name})({paramsStr});")
|
||||
dec be.indent
|
||||
be.emitLine(&"}} {ifaceName}_VTable;")
|
||||
# Fat pointer struct
|
||||
be.emitLine(&"typedef struct {ifaceName}_FatPtr {{")
|
||||
inc be.indent
|
||||
be.emitLine("void* data;")
|
||||
be.emitLine(&"{ifaceName}_VTable* vtable;")
|
||||
dec be.indent
|
||||
be.emitLine(&"}} {ifaceName}_FatPtr;")
|
||||
be.emitLine("")
|
||||
|
||||
# VTable instances
|
||||
for vt in module.vtables:
|
||||
if vt.hasAssocTypes:
|
||||
continue # Skip vtables for interfaces with associated types
|
||||
let varName = vt.concreteType & "_" & vt.interfaceName & "_VTable"
|
||||
be.emitLine(&"{vt.interfaceName}_VTable {varName} = {{")
|
||||
inc be.indent
|
||||
for m in vt.methodNames:
|
||||
be.emitLine(&".{m} = (void*){vt.concreteType}_{m},")
|
||||
dec be.indent
|
||||
be.emitLine("};")
|
||||
be.emitLine("")
|
||||
|
||||
# Function definitions
|
||||
var hasMain = false
|
||||
for f in module.funcs:
|
||||
|
||||
+28
@@ -33,6 +33,11 @@ type
|
||||
hSizeOf
|
||||
# Concurrency
|
||||
hSpawn
|
||||
# Compile-time code generation
|
||||
hEmit
|
||||
# Trait objects (dynamic dispatch)
|
||||
hDynRef
|
||||
hDynCall
|
||||
# Composite
|
||||
hBlock
|
||||
hStructInit
|
||||
@@ -112,6 +117,16 @@ type
|
||||
of hSpawn:
|
||||
spawnCallee*: string
|
||||
spawnArgs*: seq[HirNode]
|
||||
of hEmit:
|
||||
emitCode*: string
|
||||
of hDynRef:
|
||||
dynRefData*: HirNode
|
||||
dynRefInterface*: string
|
||||
dynRefConcreteType*: string
|
||||
of hDynCall:
|
||||
dynCallReceiver*: HirNode
|
||||
dynCallMethod*: string
|
||||
dynCallArgs*: seq[HirNode]
|
||||
of hBlock:
|
||||
blockStmts*: seq[HirNode]
|
||||
blockExpr*: HirNode
|
||||
@@ -153,6 +168,8 @@ type
|
||||
structs*: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]]
|
||||
enums*: seq[tuple[name: string, variants: seq[HirEnumVariant]]]
|
||||
consts*: seq[tuple[name: string, typ: Type, value: HirNode]]
|
||||
interfaces*: seq[tuple[name: string, hasAssocTypes: bool, methods: seq[tuple[name: string, params: seq[Type], ret: Type]]]]
|
||||
vtables*: seq[tuple[interfaceName: string, concreteType: string, methodNames: seq[string], hasAssocTypes: bool]]
|
||||
|
||||
# Constructor helpers
|
||||
proc hirLit*(tok: Token, typ: Type, loc: SourceLocation): HirNode =
|
||||
@@ -187,3 +204,14 @@ proc hirStore*(ptrNode, value: HirNode, loc: SourceLocation): HirNode =
|
||||
|
||||
proc hirLoad*(ptrNode: HirNode, typ: Type, loc: SourceLocation): HirNode =
|
||||
HirNode(kind: hLoad, loadPtr: ptrNode, typ: typ, loc: loc)
|
||||
|
||||
proc hirEmit*(code: string, loc: SourceLocation): HirNode =
|
||||
HirNode(kind: hEmit, emitCode: code, typ: makeVoid(), loc: loc)
|
||||
|
||||
proc hirDynRef*(data: HirNode, interfaceName, concreteType: string, loc: SourceLocation): HirNode =
|
||||
HirNode(kind: hDynRef, dynRefData: data, dynRefInterface: interfaceName,
|
||||
dynRefConcreteType: concreteType, typ: makeDynRef(interfaceName), loc: loc)
|
||||
|
||||
proc hirDynCall*(receiver: HirNode, methodName: string, args: seq[HirNode], typ: Type, loc: SourceLocation): HirNode =
|
||||
HirNode(kind: hDynCall, dynCallReceiver: receiver, dynCallMethod: methodName,
|
||||
dynCallArgs: args, typ: typ, loc: loc)
|
||||
|
||||
+116
-18
@@ -164,12 +164,16 @@ proc substituteType(ctx: var LowerCtx, te: TypeExpr, subst: Table[string, Type])
|
||||
ctx.structInstMap[mangledName] = (te.typeName, concreteArgs)
|
||||
return makeNamed(mangledName)
|
||||
return ctx.resolveTypeExpr(te)
|
||||
of tekOwn:
|
||||
return substituteType(ctx, te.pointerPointee, subst)
|
||||
of tekPointer:
|
||||
return makePointer(substituteType(ctx, te.pointerPointee, subst))
|
||||
of tekRef:
|
||||
return makeRef(substituteType(ctx, te.pointerPointee, subst))
|
||||
of tekMutRef:
|
||||
return makeMutRef(substituteType(ctx, te.pointerPointee, subst))
|
||||
of tekDynRef:
|
||||
return makeDynRef(te.dynInterface)
|
||||
of tekSlice:
|
||||
return makeSlice(substituteType(ctx, te.sliceElement, subst))
|
||||
of tekTuple:
|
||||
@@ -235,6 +239,8 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
|
||||
if ctx.typeSubst.hasKey(te.typeName):
|
||||
return ctx.typeSubst[te.typeName]
|
||||
return makeNamed(te.typeName)
|
||||
of tekOwn: return ctx.resolveTypeExpr(te.pointerPointee)
|
||||
of tekDynRef: return makeDynRef(te.dynInterface)
|
||||
of tekPointer: return makePointer(ctx.resolveTypeExpr(te.pointerPointee))
|
||||
of tekSlice: return makeSlice(ctx.resolveTypeExpr(te.sliceElement))
|
||||
else: return makeUnknown()
|
||||
@@ -309,6 +315,10 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
let methodName = expr.exprCallCallee.exprFieldName
|
||||
var typeName = ""
|
||||
if recvType.kind == tkNamed: typeName = recvType.name
|
||||
elif recvType.kind in {tkInt, tkInt8, tkInt16, tkInt32, tkInt64,
|
||||
tkUInt, tkUInt8, tkUInt16, tkUInt32, tkUInt64,
|
||||
tkFloat32, tkFloat64, tkBool, tkStr, tkChar8}:
|
||||
typeName = recvType.toString
|
||||
elif recvType.isPointer and recvType.inner.len > 0 and recvType.inner[0].kind == tkNamed:
|
||||
typeName = recvType.inner[0].name
|
||||
if typeName != "" and ctx.methodTable.hasKey(typeName):
|
||||
@@ -335,7 +345,7 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
of "float32": return makeFloat32()
|
||||
of "bool": return makeBool()
|
||||
else: return makeNamed(f.ftype.typeName)
|
||||
of tekPointer:
|
||||
of tekOwn, tekPointer:
|
||||
return ctx.resolveTypeExpr(f.ftype)
|
||||
else: return makeUnknown()
|
||||
return makeUnknown()
|
||||
@@ -376,7 +386,7 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
proc extractGenericStructInfo(ctx: LowerCtx, te: TypeExpr): tuple[baseName: string, typeArgs: seq[TypeExpr]] =
|
||||
if te == nil: return ("", @[])
|
||||
var baseTe = te
|
||||
if baseTe.kind == tekPointer:
|
||||
if baseTe.kind in {tekOwn, tekPointer}:
|
||||
baseTe = baseTe.pointerPointee
|
||||
if baseTe.kind == tekNamed and baseTe.typeArgs.len > 0 and ctx.genericStructs.hasKey(baseTe.typeName):
|
||||
return (baseTe.typeName, baseTe.typeArgs)
|
||||
@@ -399,6 +409,30 @@ proc getReceiverTypeExpr(ctx: LowerCtx, expr: Expr): TypeExpr =
|
||||
|
||||
proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs: seq[TypeExpr]): string
|
||||
|
||||
proc lowerExprWithDynRefCoerce(ctx: var LowerCtx, arg: Expr, expectedType: Type): HirNode =
|
||||
## Lower an expression, coercing &Concrete to &dyn Trait if needed.
|
||||
let lowered = ctx.lowerExpr(arg)
|
||||
if expectedType != nil and expectedType.isDynRef and arg.kind == ekUnary and arg.exprUnaryOp == tkAmp:
|
||||
let concreteType = ctx.resolveExprType(arg.exprUnaryOperand)
|
||||
var concreteName = ""
|
||||
if concreteType.kind == tkNamed:
|
||||
concreteName = concreteType.name
|
||||
elif concreteType.isPointer and concreteType.inner.len > 0 and concreteType.inner[0].kind == tkNamed:
|
||||
concreteName = concreteType.inner[0].name
|
||||
if concreteName != "":
|
||||
return hirDynRef(lowered, expectedType.name, concreteName, arg.loc)
|
||||
return lowered
|
||||
|
||||
proc lowerCallArgs(ctx: var LowerCtx, calleeExpr: Expr, argExprs: seq[Expr]): seq[HirNode] =
|
||||
## Lower call arguments with &Concrete -> &dyn Trait coercion.
|
||||
var paramTypes: seq[Type] = @[]
|
||||
let calleeType = ctx.resolveExprType(calleeExpr)
|
||||
if calleeType.kind == tkFunc and calleeType.inner.len > 1:
|
||||
paramTypes = calleeType.inner[0..^2]
|
||||
for i, arg in argExprs:
|
||||
let expected = if i < paramTypes.len: paramTypes[i] else: nil
|
||||
result.add(ctx.lowerExprWithDynRefCoerce(arg, expected))
|
||||
|
||||
proc findMethodEntry(ctx: LowerCtx, typeName: string): (string, seq[MethodInfo]) =
|
||||
if ctx.methodTable.hasKey(typeName):
|
||||
return (typeName, ctx.methodTable[typeName])
|
||||
@@ -456,6 +490,10 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
receiverTypeName = substituted.name
|
||||
elif substituted.isPointer and substituted.inner.len > 0 and substituted.inner[0].kind == tkNamed:
|
||||
receiverTypeName = substituted.inner[0].name
|
||||
elif receiverType.kind in {tkInt, tkInt8, tkInt16, tkInt32, tkInt64,
|
||||
tkUInt, tkUInt8, tkUInt16, tkUInt32, tkUInt64,
|
||||
tkFloat32, tkFloat64, tkBool, tkStr, tkChar8}:
|
||||
receiverTypeName = receiverType.toString
|
||||
elif receiverType.isPointer and receiverType.inner.len > 0 and receiverType.inner[0].kind == tkNamed:
|
||||
receiverTypeName = receiverType.inner[0].name
|
||||
|
||||
@@ -477,15 +515,24 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
args.add(hirUnary(tkAmp, loweredReceiver, makePointer(receiverType), loc))
|
||||
else:
|
||||
args.add(loweredReceiver)
|
||||
for arg in expr.exprCallArgs:
|
||||
args.add(ctx.lowerExpr(arg))
|
||||
let extraArgs = ctx.lowerCallArgs(expr.exprCallCallee, expr.exprCallArgs)
|
||||
for a in extraArgs:
|
||||
args.add(a)
|
||||
return hirCall(calleeName, args, typ, loc)
|
||||
|
||||
# Trait object virtual dispatch: &dyn Trait -> method()
|
||||
if receiverType.kind == tkDynRef:
|
||||
let loweredReceiver = ctx.lowerExpr(receiverExpr)
|
||||
var args: seq[HirNode] = @[]
|
||||
args.add(loweredReceiver)
|
||||
let extraArgs = ctx.lowerCallArgs(expr.exprCallCallee, expr.exprCallArgs)
|
||||
for a in extraArgs:
|
||||
args.add(a)
|
||||
return hirDynCall(loweredReceiver, methodName, args, typ, loc)
|
||||
|
||||
# Not a method call - treat as field access + call (function pointer)
|
||||
let callee = ctx.lowerExpr(expr.exprCallCallee)
|
||||
var args: seq[HirNode] = @[]
|
||||
for arg in expr.exprCallArgs:
|
||||
args.add(ctx.lowerExpr(arg))
|
||||
let args = ctx.lowerCallArgs(expr.exprCallCallee, expr.exprCallArgs)
|
||||
return HirNode(kind: hCallIndirect, callIndirectCallee: callee,
|
||||
callIndirectArgs: args, typ: typ, loc: loc)
|
||||
|
||||
@@ -501,9 +548,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
else:
|
||||
typeSuffix.add("unknown")
|
||||
let mangledName = baseName & "_" & typeSuffix
|
||||
var args: seq[HirNode] = @[]
|
||||
for arg in expr.exprCallArgs:
|
||||
args.add(ctx.lowerExpr(arg))
|
||||
let args = ctx.lowerCallArgs(expr.exprCallCallee, expr.exprCallArgs)
|
||||
return hirCall(mangledName, args, typ, loc)
|
||||
|
||||
# Inferred generic function call: Max(10, 20) → Max_int(10, 20)
|
||||
@@ -527,9 +572,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
else:
|
||||
typeSuffix.add("unknown")
|
||||
let mangledName = calleeName & "_" & typeSuffix
|
||||
var args: seq[HirNode] = @[]
|
||||
for arg in expr.exprCallArgs:
|
||||
args.add(ctx.lowerExpr(arg))
|
||||
let args = ctx.lowerCallArgs(expr.exprCallCallee, expr.exprCallArgs)
|
||||
return hirCall(mangledName, args, typ, loc)
|
||||
|
||||
# Regular function call
|
||||
@@ -540,9 +583,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
calleeName = ctx.importTable[calleeName]
|
||||
elif expr.exprCallCallee.kind == ekPath:
|
||||
calleeName = expr.exprCallCallee.exprPath.join("_")
|
||||
var args: seq[HirNode] = @[]
|
||||
for arg in expr.exprCallArgs:
|
||||
args.add(ctx.lowerExpr(arg))
|
||||
let args = ctx.lowerCallArgs(expr.exprCallCallee, expr.exprCallArgs)
|
||||
if calleeName != "":
|
||||
return hirCall(calleeName, args, typ, loc)
|
||||
else:
|
||||
@@ -792,6 +833,8 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
case stmt.stmtLetType.kind
|
||||
of tekNamed:
|
||||
ctx.resolveTypeExpr(stmt.stmtLetType)
|
||||
of tekOwn:
|
||||
ctx.resolveTypeExpr(stmt.stmtLetType.pointerPointee)
|
||||
of tekPointer:
|
||||
let pointeeType = ctx.resolveTypeExpr(stmt.stmtLetType.pointerPointee)
|
||||
makePointer(pointeeType)
|
||||
@@ -809,7 +852,7 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
# Track type expr for generic method inference
|
||||
if stmt.stmtLetType != nil:
|
||||
ctx.varTypeExprs[stmt.stmtLetName] = stmt.stmtLetType
|
||||
elif stmt.stmtLetInit != nil and stmt.stmtLetInit.kind == ekStructInit and stmt.stmtLetInit.exprStructInitTypeArgs.len > 0:
|
||||
elif stmt.stmtLetInit != nil and stmt.stmtLetInit.kind == ekStructInit:
|
||||
ctx.varTypeExprs[stmt.stmtLetName] = TypeExpr(
|
||||
kind: tekNamed,
|
||||
loc: stmt.stmtLetInit.loc,
|
||||
@@ -863,6 +906,15 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
return ctx.flushPending(HirNode(kind: hBreak, breakLabel: stmt.stmtBreakLabel,
|
||||
typ: makeVoid(), loc: loc))
|
||||
|
||||
of skStaticAssert, skComptime:
|
||||
# Compile-time only: evaluated in sema, no runtime code
|
||||
return nil
|
||||
|
||||
of skEmit:
|
||||
if stmt.stmtEmitEvaluated.len > 0:
|
||||
return hirEmit(stmt.stmtEmitEvaluated, loc)
|
||||
return nil
|
||||
|
||||
of skContinue:
|
||||
return ctx.flushPending(HirNode(kind: hContinue, continueLabel: stmt.stmtContinueLabel,
|
||||
typ: makeVoid(), loc: loc))
|
||||
@@ -1232,6 +1284,13 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
of dkExternFunc:
|
||||
externFuncs.add(ctx.lowerFunc(decl))
|
||||
of dkImpl:
|
||||
# Add associated type substitutions for this impl block
|
||||
var oldAssocSubst = initTable[string, Type]()
|
||||
for assoc in decl.declImplAssocTypes:
|
||||
let resolved = ctx.resolveTypeExpr(assoc.typ)
|
||||
if ctx.typeSubst.hasKey(assoc.name):
|
||||
oldAssocSubst[assoc.name] = ctx.typeSubst[assoc.name]
|
||||
ctx.typeSubst[assoc.name] = resolved
|
||||
for methodDecl in decl.declImplMethods:
|
||||
if methodDecl.kind == dkFunc:
|
||||
# Skip generic methods — they are monomorphized via generateMethodInstance
|
||||
@@ -1240,6 +1299,12 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
var hf = ctx.lowerFunc(methodDecl)
|
||||
hf.name = decl.declImplTypeName & "_" & hf.name
|
||||
funcs.add(hf)
|
||||
# Restore old substitutions
|
||||
for name, typ in oldAssocSubst:
|
||||
ctx.typeSubst[name] = typ
|
||||
for assoc in decl.declImplAssocTypes:
|
||||
if not oldAssocSubst.hasKey(assoc.name):
|
||||
ctx.typeSubst.del(assoc.name)
|
||||
of dkStruct:
|
||||
if decl.declStructTypeParams.len == 0: # Skip generic structs — monomorphized separately
|
||||
var fields: seq[tuple[name: string, typ: Type]] = @[]
|
||||
@@ -1298,4 +1363,37 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
for f in ctx.extraFuncs:
|
||||
funcs.add(f)
|
||||
|
||||
result = HirModule(funcs: funcs, externFuncs: externFuncs, structs: structs, enums: enums, consts: consts)
|
||||
# Collect interface info for vtable generation
|
||||
var ifaceInfos: seq[tuple[name: string, hasAssocTypes: bool, methods: seq[tuple[name: string, params: seq[Type], ret: Type]]]] = @[]
|
||||
for ifaceName, ifaceDecl in sema.interfaceTable:
|
||||
var methods: seq[tuple[name: string, params: seq[Type], ret: Type]] = @[]
|
||||
for m in ifaceDecl.declInterfaceMethods:
|
||||
var params: seq[Type] = @[]
|
||||
for p in m.declFuncParams:
|
||||
params.add(ctx.resolveTypeExpr(p.ptype))
|
||||
let ret = if m.declFuncReturnType != nil: ctx.resolveTypeExpr(m.declFuncReturnType) else: makeVoid()
|
||||
methods.add((m.declFuncName, params, ret))
|
||||
ifaceInfos.add((ifaceName, ifaceDecl.declInterfaceAssocTypes.len > 0, methods))
|
||||
|
||||
# Collect vtable instances: which concrete types implement which interfaces
|
||||
var vtableInfos: seq[tuple[interfaceName: string, concreteType: string, methodNames: seq[string], hasAssocTypes: bool]] = @[]
|
||||
for ifaceName, ifaceDecl in sema.interfaceTable:
|
||||
let requiredMethods = ifaceDecl.declInterfaceMethods
|
||||
let hasAssoc = ifaceDecl.declInterfaceAssocTypes.len > 0
|
||||
for typeName, methods in sema.methodTable:
|
||||
var allFound = true
|
||||
var methodNames: seq[string] = @[]
|
||||
for req in requiredMethods:
|
||||
var found = false
|
||||
for avail in methods:
|
||||
if avail.name == req.declFuncName:
|
||||
found = true
|
||||
methodNames.add(req.declFuncName)
|
||||
break
|
||||
if not found:
|
||||
allFound = false
|
||||
break
|
||||
if allFound:
|
||||
vtableInfos.add((ifaceName, typeName, methodNames, hasAssoc))
|
||||
|
||||
result = HirModule(funcs: funcs, externFuncs: externFuncs, structs: structs, enums: enums, consts: consts, interfaces: ifaceInfos, vtables: vtableInfos)
|
||||
|
||||
@@ -476,6 +476,8 @@ proc scanSymbol(lex: var Lexer, startLoc: SourceLocation): Token =
|
||||
return lex.makeToken(tkHashTime, startLoc, startPos)
|
||||
elif afterHash == 'm' and lex.matchStr("module"):
|
||||
return lex.makeToken(tkHashModule, startLoc, startPos)
|
||||
elif afterHash == 'e' and lex.matchStr("emit"):
|
||||
return lex.makeToken(tkHashEmit, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkHash, startLoc, startPos)
|
||||
else:
|
||||
|
||||
+52
-4
@@ -207,6 +207,9 @@ proc parseBaseType(p: var Parser): TypeExpr =
|
||||
discard p.expect(tkGt, "expected '>' to close type arguments")
|
||||
return TypeExpr(kind: tekNamed, loc: loc, typeName: name, typeArgs: typeArgs)
|
||||
return TypeExpr(kind: tekNamed, loc: loc, typeName: name)
|
||||
of tkOwn:
|
||||
discard p.advance()
|
||||
return TypeExpr(kind: tekOwn, loc: loc, pointerPointee: p.parseBaseType())
|
||||
of tkStar:
|
||||
discard p.advance()
|
||||
return TypeExpr(kind: tekPointer, loc: loc, pointerPointee: p.parseBaseType())
|
||||
@@ -215,6 +218,10 @@ proc parseBaseType(p: var Parser): TypeExpr =
|
||||
if p.check(tkMut):
|
||||
discard p.advance()
|
||||
return TypeExpr(kind: tekMutRef, loc: loc, pointerPointee: p.parseBaseType())
|
||||
if p.check(tkDyn):
|
||||
discard p.advance()
|
||||
let ifaceName = p.expect(tkIdent, "expected interface name after 'dyn'").text
|
||||
return TypeExpr(kind: tekDynRef, loc: loc, dynInterface: ifaceName)
|
||||
return TypeExpr(kind: tekRef, loc: loc, pointerPointee: p.parseBaseType())
|
||||
of tkLParen:
|
||||
discard p.advance()
|
||||
@@ -883,6 +890,26 @@ proc parseStmt(p: var Parser): Stmt =
|
||||
if p.check(tkSemicolon):
|
||||
discard p.advance()
|
||||
return Stmt(kind: skContinue, loc: loc, stmtContinueLabel: label)
|
||||
of tkStaticAssert:
|
||||
discard p.advance()
|
||||
let cond = p.parseExpr()
|
||||
var msg: Expr = nil
|
||||
if p.check(tkComma):
|
||||
discard p.advance()
|
||||
msg = p.parseExpr()
|
||||
if p.check(tkSemicolon):
|
||||
discard p.advance()
|
||||
return Stmt(kind: skStaticAssert, loc: loc, stmtStaticAssertCond: cond, stmtStaticAssertMsg: msg)
|
||||
of tkComptime:
|
||||
discard p.advance()
|
||||
let blk = p.parseBlock()
|
||||
return Stmt(kind: skComptime, loc: loc, stmtComptimeBlock: blk)
|
||||
of tkHashEmit:
|
||||
discard p.advance()
|
||||
let expr = p.parseExpr()
|
||||
if p.check(tkSemicolon):
|
||||
discard p.advance()
|
||||
return Stmt(kind: skEmit, loc: loc, stmtEmitExpr: expr, stmtEmitEvaluated: "")
|
||||
of tkDiscard:
|
||||
discard p.advance()
|
||||
var val: Expr = nil
|
||||
@@ -1103,16 +1130,25 @@ proc parseInterfaceDecl(p: var Parser, isPublic: bool): Decl =
|
||||
let name = p.expect(tkIdent, "expected interface name").text
|
||||
discard p.expect(tkLBrace, "expected '{' to start interface body")
|
||||
var methods: seq[Decl] = @[]
|
||||
var assocTypes: seq[string] = @[]
|
||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
||||
# Skip newlines
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
if p.check(tkRBrace) or p.isAtEnd:
|
||||
break
|
||||
methods.add(p.parseFuncDecl(false, false, ParsedAttrs()))
|
||||
if p.check(tkType):
|
||||
discard p.advance()
|
||||
let assocName = p.expect(tkIdent, "expected associated type name").text
|
||||
if p.check(tkSemicolon):
|
||||
discard p.advance()
|
||||
assocTypes.add(assocName)
|
||||
else:
|
||||
methods.add(p.parseFuncDecl(false, false, ParsedAttrs()))
|
||||
discard p.expect(tkRBrace, "expected '}' to close interface")
|
||||
return Decl(kind: dkInterface, loc: loc, isPublic: isPublic,
|
||||
declInterfaceName: name, declInterfaceMethods: methods)
|
||||
declInterfaceName: name, declInterfaceAssocTypes: assocTypes,
|
||||
declInterfaceMethods: methods)
|
||||
|
||||
proc parseImplDecl(p: var Parser): Decl =
|
||||
let loc = p.currentLoc
|
||||
@@ -1125,17 +1161,29 @@ proc parseImplDecl(p: var Parser): Decl =
|
||||
interfaceName = p.expect(tkIdent, "expected interface name").text
|
||||
discard p.expect(tkLBrace, "expected '{' to start impl block")
|
||||
var methods: seq[Decl] = @[]
|
||||
var assocTypes: seq[tuple[name: string, typ: TypeExpr]] = @[]
|
||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
||||
# Skip newlines
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
if p.check(tkRBrace) or p.isAtEnd:
|
||||
break
|
||||
methods.add(p.parseFuncDecl(false, false, ParsedAttrs()))
|
||||
if p.check(tkType):
|
||||
discard p.advance()
|
||||
let assocName = p.expect(tkIdent, "expected associated type name").text
|
||||
discard p.expect(tkAssign, "expected '=' in associated type implementation")
|
||||
let assocType = p.parseType()
|
||||
if p.check(tkSemicolon):
|
||||
discard p.advance()
|
||||
assocTypes.add((assocName, assocType))
|
||||
else:
|
||||
methods.add(p.parseFuncDecl(false, false, ParsedAttrs()))
|
||||
discard p.expect(tkRBrace, "expected '}' to close impl block")
|
||||
return Decl(kind: dkImpl, loc: loc, declImplTypeName: typeName,
|
||||
declImplTypeParams: typeParams,
|
||||
declImplInterface: interfaceName, declImplMethods: methods)
|
||||
declImplInterface: interfaceName,
|
||||
declImplAssocTypes: assocTypes,
|
||||
declImplMethods: methods)
|
||||
|
||||
proc parseModuleDecl(p: var Parser, isPublic: bool): Decl =
|
||||
let loc = p.currentLoc
|
||||
|
||||
+140
-3
@@ -48,6 +48,45 @@ type
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc unescapeStringLiteral*(s: string): string =
|
||||
## Convert a raw string literal (with surrounding quotes and escape sequences)
|
||||
## into the actual string value.
|
||||
result = s
|
||||
# Strip surrounding quotes
|
||||
if result.len >= 2 and result[0] == '"' and result[^1] == '"':
|
||||
result = result[1 ..< ^1]
|
||||
# Process escape sequences
|
||||
var i = 0
|
||||
var outStr = ""
|
||||
while i < result.len:
|
||||
if result[i] == '\\' and i + 1 < result.len:
|
||||
case result[i + 1]
|
||||
of '\\': outStr.add('\\')
|
||||
of '"': outStr.add('"')
|
||||
of '\'': outStr.add('\'')
|
||||
of 'n': outStr.add('\n')
|
||||
of 'r': outStr.add('\r')
|
||||
of 't': outStr.add('\t')
|
||||
of '0': outStr.add('\0')
|
||||
of 'x':
|
||||
if i + 3 < result.len:
|
||||
let hexStr = result[i + 2 .. i + 3]
|
||||
try:
|
||||
let code = parseHexInt(hexStr)
|
||||
outStr.add(chr(code))
|
||||
i += 2
|
||||
except ValueError:
|
||||
outStr.add(result[i])
|
||||
else:
|
||||
outStr.add(result[i])
|
||||
else:
|
||||
outStr.add(result[i + 1])
|
||||
i += 2
|
||||
else:
|
||||
outStr.add(result[i])
|
||||
inc i
|
||||
result = outStr
|
||||
|
||||
proc emitError(sema: var Sema, loc: SourceLocation, message: string) =
|
||||
sema.diagnostics.add(SemaDiagnostic(severity: sdsError, loc: loc, message: message))
|
||||
|
||||
@@ -76,8 +115,10 @@ proc typeExprReferencesTypeParam(te: TypeExpr, name: string): bool =
|
||||
return false
|
||||
of tekSlice:
|
||||
return typeExprReferencesTypeParam(te.sliceElement, name)
|
||||
of tekPointer, tekRef, tekMutRef:
|
||||
of tekOwn, tekPointer, tekRef, tekMutRef:
|
||||
return typeExprReferencesTypeParam(te.pointerPointee, name)
|
||||
of tekDynRef:
|
||||
return false
|
||||
of tekTuple:
|
||||
for elem in te.tupleElements:
|
||||
if typeExprReferencesTypeParam(elem, name): return true
|
||||
@@ -132,7 +173,7 @@ proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type],
|
||||
if i >= argTypes.len: break
|
||||
# Skip pointer params — type param is inside the pointee and we cannot
|
||||
# structurally extract it (e.g., *Map<K,V> → arg is *Map<int,String>)
|
||||
if param.ptype.kind == tekPointer:
|
||||
if param.ptype.kind in {tekOwn, tekPointer}:
|
||||
continue
|
||||
if typeExprReferencesTypeParam(param.ptype, tpName):
|
||||
if inferred == nil:
|
||||
@@ -194,12 +235,16 @@ proc resolveType(sema: var Sema, te: TypeExpr): Type =
|
||||
of tekPath:
|
||||
let fullName = te.pathSegments.join("::")
|
||||
return makeNamed(fullName)
|
||||
of tekOwn:
|
||||
return sema.resolveType(te.pointerPointee)
|
||||
of tekPointer:
|
||||
return makePointer(sema.resolveType(te.pointerPointee))
|
||||
of tekRef:
|
||||
return makeRef(sema.resolveType(te.pointerPointee))
|
||||
of tekMutRef:
|
||||
return makeMutRef(sema.resolveType(te.pointerPointee))
|
||||
of tekDynRef:
|
||||
return makeDynRef(te.dynInterface)
|
||||
of tekSlice:
|
||||
let elemType = sema.resolveType(te.sliceElement)
|
||||
return makeSlice(elemType)
|
||||
@@ -253,6 +298,20 @@ proc evalBlock(sema: Sema, blk: Block, locals: Table[string, CtValue]): CtValue
|
||||
let res = sema.evalExpr(stmt.stmtExpr, localVars)
|
||||
if res.kind != ctkVoid:
|
||||
return res
|
||||
of skStaticAssert:
|
||||
let cond = sema.evalExpr(stmt.stmtStaticAssertCond, localVars)
|
||||
if cond.kind != ctkBool or not cond.boolVal:
|
||||
var msg = "static assertion failed"
|
||||
if stmt.stmtStaticAssertMsg != nil:
|
||||
let msgVal = sema.evalExpr(stmt.stmtStaticAssertMsg, localVars)
|
||||
if msgVal.kind == ctkString:
|
||||
msg = msgVal.strVal
|
||||
# Note: we can't emitError here because evalBlock is used for const folding too
|
||||
# and we don't have access to sema diagnostics. For now, just return void.
|
||||
# In checkStmt we'll do the real error reporting.
|
||||
discard
|
||||
of skComptime:
|
||||
discard sema.evalBlock(stmt.stmtComptimeBlock, localVars)
|
||||
else:
|
||||
discard
|
||||
return CtValue(kind: ctkVoid)
|
||||
@@ -268,7 +327,7 @@ proc evalExpr(sema: Sema, expr: Expr, locals: Table[string, CtValue]): CtValue =
|
||||
of tkBoolLiteral:
|
||||
return CtValue(kind: ctkBool, boolVal: expr.exprLit.text == "true")
|
||||
of tkStringLiteral:
|
||||
return CtValue(kind: ctkString, strVal: expr.exprLit.text)
|
||||
return CtValue(kind: ctkString, strVal: unescapeStringLiteral(expr.exprLit.text))
|
||||
else:
|
||||
return CtValue(kind: ctkVoid)
|
||||
of ekIdent:
|
||||
@@ -508,6 +567,9 @@ proc collectGlobals*(sema: var Sema) =
|
||||
if not sema.globalScope.define(sym):
|
||||
sema.emitError(decl.loc, &"duplicate symbol '{decl.declInterfaceName}'")
|
||||
sema.typeTable[decl.declInterfaceName] = t
|
||||
# Register associated types as type parameters (they get substituted in impl)
|
||||
for assoc in decl.declInterfaceAssocTypes:
|
||||
sema.typeTable[assoc] = makeTypeParam(assoc)
|
||||
of dkImpl:
|
||||
# Register methods for the type
|
||||
let typeName = decl.declImplTypeName
|
||||
@@ -584,6 +646,14 @@ proc typeImplements(sema: Sema, t: Type, interfaceName: string): bool =
|
||||
break
|
||||
if not found:
|
||||
return false
|
||||
# Check associated types (permissive in bootstrap — just check if impl has them)
|
||||
for assoc in iface.declInterfaceAssocTypes:
|
||||
var found = false
|
||||
# Look for impl block that provides this associated type
|
||||
# This is a simplified check; full impl lookup would require tracking impl blocks
|
||||
found = true # Be permissive in bootstrap
|
||||
if not found:
|
||||
return false
|
||||
return true
|
||||
|
||||
proc checkTraitBounds(sema: var Sema, funcDecl: Decl, inferredTypes: seq[Type], loc: SourceLocation) =
|
||||
@@ -779,6 +849,10 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
var typeName = ""
|
||||
if receiver.kind == tkNamed:
|
||||
typeName = receiver.name
|
||||
elif receiver.kind in {tkInt, tkInt8, tkInt16, tkInt32, tkInt64,
|
||||
tkUInt, tkUInt8, tkUInt16, tkUInt32, tkUInt64,
|
||||
tkFloat32, tkFloat64, tkBool, tkStr, tkChar8}:
|
||||
typeName = receiver.toString
|
||||
elif receiver.isPointer and receiver.inner.len > 0 and receiver.inner[0].kind == tkNamed:
|
||||
typeName = receiver.inner[0].name
|
||||
|
||||
@@ -799,6 +873,28 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
sema.emitError(expr.loc, &"argument {i+1}: expected {expectedParams[paramIdx].toString}, got {argTypes[i].toString}")
|
||||
return minfo.retType
|
||||
|
||||
# Trait object virtual method call: &dyn Trait
|
||||
if receiver.kind == tkDynRef:
|
||||
let ifaceName = receiver.name
|
||||
if sema.interfaceTable.hasKey(ifaceName):
|
||||
let iface = sema.interfaceTable[ifaceName]
|
||||
for m in iface.declInterfaceMethods:
|
||||
if m.declFuncName == methodName:
|
||||
var paramTypes: seq[Type] = @[]
|
||||
for p in m.declFuncParams:
|
||||
paramTypes.add(sema.resolveType(p.ptype))
|
||||
if argTypes.len + 1 < paramTypes.len:
|
||||
sema.emitError(expr.loc, &"too few arguments for method '{methodName}'")
|
||||
elif argTypes.len > paramTypes.len:
|
||||
sema.emitError(expr.loc, &"too many arguments for method '{methodName}'")
|
||||
else:
|
||||
for i in 0 ..< argTypes.len:
|
||||
let paramIdx = i + 1
|
||||
if paramIdx < paramTypes.len:
|
||||
if not argTypes[i].isAssignableTo(paramTypes[paramIdx]) and not (argTypes[i].kind in {TypeKind.tkUnknown, TypeKind.tkNamed, TypeKind.tkTypeParam}):
|
||||
sema.emitError(expr.loc, &"argument {i+1}: expected {paramTypes[paramIdx].toString}, got {argTypes[i].toString}")
|
||||
return if m.declFuncReturnType != nil: sema.resolveType(m.declFuncReturnType) else: makeVoid()
|
||||
|
||||
# Not a method - treat as function pointer field
|
||||
let fieldType = sema.checkExpr(expr.exprCallCallee, scope)
|
||||
if fieldType.kind == tkFunc:
|
||||
@@ -941,6 +1037,22 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
sema.emitError(expr.loc, &"cannot access field on type {obj.toString}")
|
||||
else:
|
||||
sema.emitError(expr.loc, &"cannot access field on type {obj.toString}")
|
||||
elif objType.kind == tkDynRef:
|
||||
# Trait object: methods come from the interface
|
||||
let ifaceName = objType.name
|
||||
if sema.interfaceTable.hasKey(ifaceName):
|
||||
let iface = sema.interfaceTable[ifaceName]
|
||||
for m in iface.declInterfaceMethods:
|
||||
if m.declFuncName == expr.exprFieldName:
|
||||
# Build function type from method signature
|
||||
var paramTypes: seq[Type] = @[]
|
||||
for p in m.declFuncParams:
|
||||
paramTypes.add(sema.resolveType(p.ptype))
|
||||
let retType = if m.declFuncReturnType != nil: sema.resolveType(m.declFuncReturnType) else: makeVoid()
|
||||
return makeFunc(paramTypes, retType)
|
||||
sema.emitError(expr.loc, &"interface '{ifaceName}' has no method '{expr.exprFieldName}'")
|
||||
else:
|
||||
sema.emitError(expr.loc, &"unknown interface '{ifaceName}'")
|
||||
else:
|
||||
sema.emitError(expr.loc, &"cannot access field on type {obj.toString}")
|
||||
return makeUnknown()
|
||||
@@ -1085,6 +1197,31 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
|
||||
return makeVoid()
|
||||
of skBreak, skContinue:
|
||||
return makeVoid()
|
||||
of skStaticAssert:
|
||||
let condType = sema.checkExpr(stmt.stmtStaticAssertCond, scope)
|
||||
if not condType.isBool:
|
||||
sema.emitError(stmt.loc, "static_assert condition must be bool")
|
||||
let condVal = sema.evalExpr(stmt.stmtStaticAssertCond, initTable[string, CtValue]())
|
||||
if condVal.kind == ctkBool and not condVal.boolVal:
|
||||
var msg = "static assertion failed"
|
||||
if stmt.stmtStaticAssertMsg != nil:
|
||||
let msgVal = sema.evalExpr(stmt.stmtStaticAssertMsg, initTable[string, CtValue]())
|
||||
if msgVal.kind == ctkString:
|
||||
msg = msgVal.strVal
|
||||
sema.emitError(stmt.loc, msg)
|
||||
return makeVoid()
|
||||
of skComptime:
|
||||
discard sema.evalBlock(stmt.stmtComptimeBlock, initTable[string, CtValue]())
|
||||
return makeVoid()
|
||||
of skEmit:
|
||||
let exprType = sema.checkExpr(stmt.stmtEmitExpr, scope)
|
||||
# Try to evaluate at compile time; if it evaluates to a string, we're good
|
||||
let val = sema.evalExpr(stmt.stmtEmitExpr, initTable[string, CtValue]())
|
||||
if val.kind == ctkString:
|
||||
stmt.stmtEmitEvaluated = val.strVal
|
||||
elif not exprType.isUnknown and exprType.kind != tkStr:
|
||||
sema.emitError(stmt.loc, "#emit requires a string expression")
|
||||
return makeVoid()
|
||||
of skDecl:
|
||||
# Local declaration inside block
|
||||
case stmt.stmtDecl.kind
|
||||
|
||||
+12
-1
@@ -55,6 +55,9 @@ type
|
||||
tkAsync # async
|
||||
tkAwait # await
|
||||
tkSpawn # spawn
|
||||
tkStaticAssert # static_assert
|
||||
tkComptime # comptime
|
||||
tkDyn # dyn
|
||||
|
||||
##Punctuation
|
||||
tkLParen # (
|
||||
@@ -129,6 +132,7 @@ type
|
||||
tkHashDate # #date
|
||||
tkHashTime # #time
|
||||
tkHashModule # #module
|
||||
tkHashEmit # #emit
|
||||
|
||||
##Special
|
||||
tkNewLine # significant newline (if grammar uses them)
|
||||
@@ -146,7 +150,7 @@ proc isKeyword*(kind: TokenKind): bool =
|
||||
tkBreak, tkContinue, tkReturn, tkMatch,
|
||||
tkFunc, tkLet, tkVar, tkConst, tkType, tkStruct, tkEnum,
|
||||
tkUnion, tkInterface, tkExtend, tkModule, tkImport,
|
||||
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkMut, tkDiscard, tkAsync, tkAwait, tkSpawn:
|
||||
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkMut, tkDiscard, tkAsync, tkAwait, tkSpawn, tkStaticAssert, tkComptime, tkDyn:
|
||||
true
|
||||
else:
|
||||
false
|
||||
@@ -206,6 +210,9 @@ proc keywordKind*(text: string): TokenKind =
|
||||
of "async": tkAsync
|
||||
of "await": tkAwait
|
||||
of "spawn": tkSpawn
|
||||
of "static_assert": tkStaticAssert
|
||||
of "comptime": tkComptime
|
||||
of "dyn": tkDyn
|
||||
of "true", "false": tkBoolLiteral
|
||||
else: tkIdent
|
||||
|
||||
@@ -255,6 +262,9 @@ proc tokenKindName*(kind: TokenKind): string =
|
||||
of tkAsync: "'async'"
|
||||
of tkAwait: "'await'"
|
||||
of tkSpawn: "'spawn'"
|
||||
of tkStaticAssert: "'static_assert'"
|
||||
of tkComptime: "'comptime'"
|
||||
of tkDyn: "'dyn'"
|
||||
of tkLParen: "'('"
|
||||
of tkRParen: "')'"
|
||||
of tkLBrace: "'{'"
|
||||
@@ -315,6 +325,7 @@ proc tokenKindName*(kind: TokenKind): string =
|
||||
of tkHashDate: "'#date'"
|
||||
of tkHashTime: "'#time'"
|
||||
of tkHashModule: "'#module'"
|
||||
of tkHashEmit: "'#emit'"
|
||||
of tkNewLine: "newline"
|
||||
of tkEndOfFile: "end of file"
|
||||
of tkUnknown: "unknown token"
|
||||
|
||||
+11
-1
@@ -27,6 +27,7 @@ type
|
||||
tkPointer
|
||||
tkRef
|
||||
tkMutRef
|
||||
tkDynRef
|
||||
tkSlice
|
||||
tkRange
|
||||
tkTuple
|
||||
@@ -69,6 +70,8 @@ proc makeRef*(pointee: Type): Type =
|
||||
Type(kind: tkRef, inner: @[pointee])
|
||||
proc makeMutRef*(pointee: Type): Type =
|
||||
Type(kind: tkMutRef, inner: @[pointee])
|
||||
proc makeDynRef*(interfaceName: string): Type =
|
||||
Type(kind: tkDynRef, name: interfaceName)
|
||||
proc makeSlice*(element: Type): Type =
|
||||
Type(kind: tkSlice, inner: @[element])
|
||||
proc makeRange*(element: Type): Type =
|
||||
@@ -101,10 +104,11 @@ proc isFloat*(t: Type): bool =
|
||||
proc isSigned*(t: Type): bool =
|
||||
if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
|
||||
t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt}
|
||||
proc isPointer*(t: Type): bool = t.kind in {tkPointer, tkRef, tkMutRef}
|
||||
proc isPointer*(t: Type): bool = t.kind in {tkPointer, tkRef, tkMutRef, tkDynRef}
|
||||
proc isRawPointer*(t: Type): bool = t.kind == tkPointer
|
||||
proc isRef*(t: Type): bool = t.kind == tkRef
|
||||
proc isMutRef*(t: Type): bool = t.kind == tkMutRef
|
||||
proc isDynRef*(t: Type): bool = t.kind == tkDynRef
|
||||
proc isSlice*(t: Type): bool = t.kind == tkSlice
|
||||
|
||||
# Comparison
|
||||
@@ -173,6 +177,11 @@ proc isAssignableTo*(a, b: Type): bool =
|
||||
if a.isRef and b.isRawPointer:
|
||||
if a.inner.len > 0 and b.inner.len > 0 and a.inner[0].isAssignableTo(b.inner[0]):
|
||||
return true
|
||||
# &Concrete -> &dyn Trait (trait object coercion)
|
||||
if b.isDynRef and a.isRef:
|
||||
return true
|
||||
if b.isDynRef and a.isMutRef:
|
||||
return true
|
||||
return false
|
||||
|
||||
# String representation
|
||||
@@ -203,6 +212,7 @@ proc toString*(t: Type): string =
|
||||
of tkPointer: "*" & t.inner[0].toString
|
||||
of tkRef: "&" & t.inner[0].toString
|
||||
of tkMutRef: "&mut " & t.inner[0].toString
|
||||
of tkDynRef: "&dyn " & t.name
|
||||
of tkSlice:
|
||||
if t.inner.len > 0: t.inner[0].toString & "[]"
|
||||
else: "Slice<?>"
|
||||
|
||||
Reference in New Issue
Block a user