fix(bootstrap): resolve lir lower crashes and struct-copy bugs

- Fix lirValToC crash on lvkInt in hStore raw C emission
- Add hAlloca handling to lowerExpr (returns &name, avoids unhandled fallback)
- Fix nested comment syntax in unhandled expr fallback
- Rewrite buildLval to handle hIndexPtr and nested hFieldPtr/hArrowField
  so assignments like arr[i].field = val emit direct C lvalues
- Make &&/|| temporaries unique (__and_tmp_N/__or_tmp_N) to avoid
  duplicate declarations in the same function
- Selfhost null-deref fix in sema.bux (unchained nested conditions)
This commit is contained in:
2026-06-06 21:46:25 +03:00
parent 9416c486b2
commit 3ae8c60ebb
6 changed files with 194 additions and 32 deletions
+2 -2
View File
@@ -11,7 +11,7 @@ all: build
build: build:
$(NIM) c -o:$(OUT) -d:release --opt:size $(SRC) $(NIM) c -o:$(OUT) -d:release --opt:size $(SRC)
strip $(OUT) # strip $(OUT)
dev: dev:
$(NIM) c -o:buxc_debug -d:debug --stackTrace:on --lineTrace:on $(SRC) $(NIM) c -o:buxc_debug -d:debug --stackTrace:on --lineTrace:on $(SRC)
@@ -72,5 +72,5 @@ selfhost: build
@cp src/bux.toml build/selfhost/ @cp src/bux.toml build/selfhost/
@mv build/selfhost/src/main.bux build/selfhost/src/Main.bux 2>/dev/null || true @mv build/selfhost/src/main.bux build/selfhost/src/Main.bux 2>/dev/null || true
@cd build/selfhost && ../../$(OUT) build @cd build/selfhost && ../../$(OUT) build
@strip build/selfhost/build/buxc2 2>/dev/null || true # strip removed for debug
@echo "=== Self-hosted compiler built successfully ===" @echo "=== Self-hosted compiler built successfully ==="
+1 -1
View File
@@ -488,7 +488,7 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
# Compile with cc # Compile with cc
let outputName = if pctx.man.name != "": pctx.man.name else: "bux_out" let outputName = if pctx.man.name != "": pctx.man.name else: "bux_out"
let outputFile = buildDir / outputName let outputFile = buildDir / outputName
let ccCmd = &"cc -O2 -pthread -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1" let ccCmd = &"cc -O0 -g -pthread -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1"
if opts.verbose: if opts.verbose:
printInfo(&"running: {ccCmd}", useColor) printInfo(&"running: {ccCmd}", useColor)
let (output, exitCode) = execCmdEx(ccCmd) let (output, exitCode) = execCmdEx(ccCmd)
+4
View File
@@ -203,6 +203,10 @@ proc hirReturn*(value: HirNode, loc: SourceLocation): HirNode =
proc hirBlock*(stmts: seq[HirNode], expr: HirNode, typ: Type, loc: SourceLocation, isScope: bool = false): HirNode = proc hirBlock*(stmts: seq[HirNode], expr: HirNode, typ: Type, loc: SourceLocation, isScope: bool = false): HirNode =
HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr, typ: typ, loc: loc, isScope: isScope) HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr, typ: typ, loc: loc, isScope: isScope)
proc hirIf*(cond, thenBranch, elseBranch: HirNode, loc: SourceLocation): HirNode =
HirNode(kind: hIf, ifCond: cond, ifThen: thenBranch, ifElse: elseBranch, typ: makeVoid(), loc: loc)
proc hirAlloca*(name: string, typ: Type, loc: SourceLocation): HirNode = proc hirAlloca*(name: string, typ: Type, loc: SourceLocation): HirNode =
HirNode(kind: hAlloca, allocaType: typ, allocaName: name, typ: makePointer(typ), loc: loc) HirNode(kind: hAlloca, allocaType: typ, allocaName: name, typ: makePointer(typ), loc: loc)
+25 -3
View File
@@ -539,9 +539,31 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
return hirUnary(expr.exprUnaryOp, operand, typ, loc) return hirUnary(expr.exprUnaryOp, operand, typ, loc)
of ekBinary: of ekBinary:
let left = ctx.lowerExpr(expr.exprBinaryLeft) case expr.exprBinaryOp
let right = ctx.lowerExpr(expr.exprBinaryRight) of tkAmpAmp:
return hirBinary(expr.exprBinaryOp, left, right, typ, loc) # Short-circuit &&: use if-then-else to avoid evaluating right when left is false
let tmp = hirAlloca("__and_tmp_" & $ctx.varCounter, makeBool(), loc)
inc ctx.varCounter
let left = ctx.lowerExpr(expr.exprBinaryLeft)
let thenBlock = hirBlock(@[hirStore(tmp, ctx.lowerExpr(expr.exprBinaryRight), loc)], nil, makeVoid(), loc)
let falseTok = Token(kind: tkBoolLiteral, text: "false", loc: loc)
let elseBlock = hirBlock(@[hirStore(tmp, hirLit(falseTok, makeBool(), loc), loc)], nil, makeVoid(), loc)
let ifNode = hirIf(left, thenBlock, elseBlock, loc)
return hirBlock(@[tmp, ifNode], hirLoad(tmp, makeBool(), loc), makeBool(), loc)
of tkPipePipe:
# Short-circuit ||: use if-then-else to avoid evaluating right when left is true
let tmp = hirAlloca("__or_tmp_" & $ctx.varCounter, makeBool(), loc)
inc ctx.varCounter
let left = ctx.lowerExpr(expr.exprBinaryLeft)
let trueTok = Token(kind: tkBoolLiteral, text: "true", loc: loc)
let thenBlock = hirBlock(@[hirStore(tmp, hirLit(trueTok, makeBool(), loc), loc)], nil, makeVoid(), loc)
let elseBlock = hirBlock(@[hirStore(tmp, ctx.lowerExpr(expr.exprBinaryRight), loc)], nil, makeVoid(), loc)
let ifNode = hirIf(left, thenBlock, elseBlock, loc)
return hirBlock(@[tmp, ifNode], hirLoad(tmp, makeBool(), loc), makeBool(), loc)
else:
let left = ctx.lowerExpr(expr.exprBinaryLeft)
let right = ctx.lowerExpr(expr.exprBinaryRight)
return hirBinary(expr.exprBinaryOp, left, right, typ, loc)
of ekCall: of ekCall:
# Method call desugaring: obj.method(args) → Type_method(obj, args) # Method call desugaring: obj.method(args) → Type_method(obj, args)
+157 -25
View File
@@ -5,6 +5,20 @@
import std/[strutils, strformat, tables, sequtils] import std/[strutils, strformat, tables, sequtils]
import ast, types, token, hir, lir import ast, types, token, hir, lir
## Convert LirValue to C expression string (no % prefix)
proc lirValToC(v: LirValue): string =
case v.kind
of lvkTemp: v.strVal
of lvkVar: v.strVal
of lvkInt: $v.intVal
of lvkFloat: $v.floatVal
of lvkString: v.strVal
of lvkLabel: v.strVal
of lvkGlobal: v.strVal
of lvkField: v.strVal
of lvkVoid: ""
of lvkType: v.strVal
type type
LowerToLirCtx* = object LowerToLirCtx* = object
builder*: LirBuilder builder*: LirBuilder
@@ -172,6 +186,16 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
return ctx.varLirValues[name] return ctx.varLirValues[name]
return lirVar(name) return lirVar(name)
# ── Alloca (address of local) ──
of hAlloca:
let cType = typeToCStr(node.allocaType)
let name = node.allocaName
if not ctx.varLirValues.hasKey(name):
ctx.varTypes[name] = cType
ctx.varLirValues[name] = lirVar(name)
b.emitAlloca(name, cType)
return lirVar("&" & name)
# ── Self ── # ── Self ──
of hSelf: of hSelf:
return lirVar("self") return lirVar("self")
@@ -198,7 +222,37 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
b.emitLoad(t, operand) b.emitLoad(t, operand)
return t return t
of tkAmp: of tkAmp:
# Address of: &var # Address of: &expr
# Optimize: &struct.field → fieldPtr (no temp copy)
# &array[i] → indexPtr (no temp copy)
if node.unaryOperand.kind == hLoad and node.unaryOperand.loadPtr != nil:
let ptrNode = node.unaryOperand.loadPtr
case ptrNode.kind
of hFieldPtr:
let base = lowerExpr(ctx, ptrNode.fieldPtrBase)
let baseTyp = ptrNode.fieldPtrBase.typ
let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef}
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
if isPtr:
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}->{ptrNode.fieldName});")
else:
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}.{ptrNode.fieldName});")
return t
of hArrowField:
let base = lowerExpr(ctx, ptrNode.arrowFieldBase)
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}->{ptrNode.arrowFieldName});")
return t
of hIndexPtr:
let base = lowerExpr(ctx, ptrNode.indexPtrBase)
let idx = lowerExpr(ctx, ptrNode.indexPtrIndex)
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}[{lirValToC(idx)}]);")
return t
else: discard
let t = b.freshTemp() let t = b.freshTemp()
b.emitAddrOf(t, operand) b.emitAddrOf(t, operand)
return t return t
@@ -215,18 +269,30 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
b.emitCmp(cmpOpToLir(node.binaryOp), t, left, right) b.emitCmp(cmpOpToLir(node.binaryOp), t, left, right)
return t return t
of tkAmpAmp, tkPipePipe: of tkAmpAmp, tkPipePipe:
# Logical and/or: lowered to select # Logical and/or: lowered to branches for short-circuit evaluation
let t = b.freshTemp() let t = b.freshTemp()
b.emitAlloca(t.strVal, "int")
let falseLbl = b.freshLabel("and_false")
let trueLbl = b.freshLabel("and_true")
let endLbl = b.freshLabel("and_end")
if node.binaryOp == tkAmpAmp: if node.binaryOp == tkAmpAmp:
# left && right → left ? (right != 0) : 0 # left && right: if !left goto false; if !right goto false; t=1; goto end; false: t=0; end:
let rhsBool = b.freshTemp() b.emitJz(falseLbl, left)
b.emitCmp(lirCmpNe, rhsBool, right, lirInt(0)) b.emitJz(falseLbl, right)
b.emitSelect(t, left, rhsBool, lirInt(0)) b.emitMov(t, lirInt(1))
b.emitJmp(endLbl)
b.emitLabel(falseLbl)
b.emitMov(t, lirInt(0))
b.emitLabel(endLbl)
else: else:
# left || right left ? 1 : (right != 0) # left || right: if left goto true; if right goto true; t=0; goto end; true: t=1; end:
let rhsBool = b.freshTemp() b.emitJnz(trueLbl, left)
b.emitCmp(lirCmpNe, rhsBool, right, lirInt(0)) b.emitJnz(trueLbl, right)
b.emitSelect(t, left, lirInt(1), rhsBool) b.emitMov(t, lirInt(0))
b.emitJmp(endLbl)
b.emitLabel(trueLbl)
b.emitMov(t, lirInt(1))
b.emitLabel(endLbl)
return t return t
else: else:
let t = b.freshTemp() let t = b.freshTemp()
@@ -269,16 +335,16 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
let t = b.freshTemp() let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*") b.emitAlloca(t.strVal, "void*")
if isPtr: if isPtr:
b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}->{node.fieldName});") b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}->{node.fieldName});")
else: else:
b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}.{node.fieldName});") b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}.{node.fieldName});")
return t return t
of hArrowField: of hArrowField:
let base = lowerExpr(ctx, node.arrowFieldBase) let base = lowerExpr(ctx, node.arrowFieldBase)
let t = b.freshTemp() let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*") b.emitAlloca(t.strVal, "void*")
b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}->{node.arrowFieldName});") b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}->{node.arrowFieldName});")
return t return t
of hIndexPtr: of hIndexPtr:
@@ -286,7 +352,7 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
let idx = lowerExpr(ctx, node.indexPtrIndex) let idx = lowerExpr(ctx, node.indexPtrIndex)
let t = b.freshTemp() let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*") b.emitAlloca(t.strVal, "void*")
b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}[{idx.strVal}]);") b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}[{lirValToC(idx)}]);")
return t return t
# ── Load ── # ── Load ──
@@ -298,7 +364,7 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
let cType = hirTypeToC(ctx, node) let cType = hirTypeToC(ctx, node)
let t = b.freshTemp() let t = b.freshTemp()
b.emitAlloca(t.strVal, cType) b.emitAlloca(t.strVal, cType)
b.emitRawC(&"{t.strVal} = {base.strVal}->{node.loadPtr.arrowFieldName};") b.emitRawC(&"{t.strVal} = {lirValToC(base)}->{node.loadPtr.arrowFieldName};")
return t return t
if node.loadPtr != nil and node.loadPtr.kind == hFieldPtr: if node.loadPtr != nil and node.loadPtr.kind == hFieldPtr:
let base = lowerExpr(ctx, node.loadPtr.fieldPtrBase) let base = lowerExpr(ctx, node.loadPtr.fieldPtrBase)
@@ -308,9 +374,9 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
let t = b.freshTemp() let t = b.freshTemp()
b.emitAlloca(t.strVal, cType) b.emitAlloca(t.strVal, cType)
if isPtr: if isPtr:
b.emitRawC(&"{t.strVal} = {base.strVal}->{node.loadPtr.fieldName};") b.emitRawC(&"{t.strVal} = {lirValToC(base)}->{node.loadPtr.fieldName};")
else: else:
b.emitRawC(&"{t.strVal} = {base.strVal}.{node.loadPtr.fieldName};") b.emitRawC(&"{t.strVal} = {lirValToC(base)}.{node.loadPtr.fieldName};")
return t return t
if node.loadPtr != nil and node.loadPtr.kind == hIndexPtr: if node.loadPtr != nil and node.loadPtr.kind == hIndexPtr:
let base = lowerExpr(ctx, node.loadPtr.indexPtrBase) let base = lowerExpr(ctx, node.loadPtr.indexPtrBase)
@@ -318,14 +384,14 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
let cType = hirTypeToC(ctx, node) let cType = hirTypeToC(ctx, node)
let t = b.freshTemp() let t = b.freshTemp()
b.emitAlloca(t.strVal, cType) b.emitAlloca(t.strVal, cType)
b.emitRawC(&"{t.strVal} = {base.strVal}[{idx.strVal}];") b.emitRawC(&"{t.strVal} = {lirValToC(base)}[{lirValToC(idx)}];")
return t return t
# Generic: dereference pointer # Generic: dereference pointer
let ptrVal = lowerExpr(ctx, node.loadPtr) let ptrVal = lowerExpr(ctx, node.loadPtr)
let cType = hirTypeToC(ctx, node) let cType = hirTypeToC(ctx, node)
let t = b.freshTemp() let t = b.freshTemp()
b.emitAlloca(t.strVal, cType) b.emitAlloca(t.strVal, cType)
b.emitRawC(&"{t.strVal} = *({cType}*){ptrVal.strVal};") b.emitRawC(&"{t.strVal} = *({cType}*){lirValToC(ptrVal)};")
return t return t
# ── Slice Index ── # ── Slice Index ──
@@ -335,7 +401,7 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
let t = b.freshTemp() let t = b.freshTemp()
# Emit: base.data[idx] (with optional bounds check) # Emit: base.data[idx] (with optional bounds check)
if node.sliceIndexBoundsCheck: if node.sliceIndexBoundsCheck:
b.emitRawC(&"bux_bounds_check((size_t)({idx.strVal}), ({base.strVal}).len)") b.emitRawC(&"bux_bounds_check((size_t)({lirValToC(idx)}), ({lirValToC(base)}).len)")
b.emit(LirInstr(kind: lirLoad, dst: t, src: base, src2: idx)) b.emit(LirInstr(kind: lirLoad, dst: t, src: base, src2: idx))
return t return t
@@ -389,7 +455,7 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
for i in 1 ..< node.dynCallArgs.len: for i in 1 ..< node.dynCallArgs.len:
args.add(lowerExpr(ctx, node.dynCallArgs[i])) args.add(lowerExpr(ctx, node.dynCallArgs[i]))
let t = b.freshTemp() let t = b.freshTemp()
b.emitRawC(&"{t.strVal} = {receiver.strVal}.vtable->{node.dynCallMethod}({args.mapIt($it).join(\", \")});") b.emitRawC(&"{t.strVal} = {lirValToC(receiver)}.vtable->{node.dynCallMethod}({args.mapIt($it).join(\", \")});")
return t return t
# ── StructInit ── # ── StructInit ──
@@ -453,9 +519,39 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
else: else:
# Fallback for unhandled expression kinds # Fallback for unhandled expression kinds
b.emitComment(&"/* unhandled expr kind: {node.kind} */") b.emitComment(&"unhandled expr kind: {node.kind}")
return lirInt(0) return lirInt(0)
# ── Build C lvalue string for direct field/index assignment ──
proc buildLval(ctx: var LowerToLirCtx, n: HirNode): string =
case n.kind
of hLoad:
if n.loadPtr != nil:
return buildLval(ctx, n.loadPtr)
else:
let v = lowerExpr(ctx, n)
return lirValToC(v)
of hVar:
return n.varName
of hSelf:
return "self"
of hFieldPtr:
let baseStr = buildLval(ctx, n.fieldPtrBase)
let baseTyp = n.fieldPtrBase.typ
let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef}
let sep = if isPtr: "->" else: "."
return baseStr & sep & n.fieldName
of hArrowField:
let baseStr = buildLval(ctx, n.arrowFieldBase)
return baseStr & "->" & n.arrowFieldName
of hIndexPtr:
let baseStr = buildLval(ctx, n.indexPtrBase)
let idx = lowerExpr(ctx, n.indexPtrIndex)
return baseStr & "[" & lirValToC(idx) & "]"
else:
let v = lowerExpr(ctx, n)
return lirValToC(v)
# ── Lowering: Statements → void ── # ── Lowering: Statements → void ──
proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) = proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) =
@@ -557,24 +653,60 @@ proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) =
let val = lowerExpr(ctx, node.storeValue) let val = lowerExpr(ctx, node.storeValue)
# ptrVal is a void* address; cast and store # ptrVal is a void* address; cast and store
let valCType = hirTypeToC(ctx, node.storeValue) let valCType = hirTypeToC(ctx, node.storeValue)
b.emitRawC(&"*({valCType}*){ptrVal.strVal} = {val.strVal};") b.emitRawC(&"*({valCType}*){lirValToC(ptrVal)} = {lirValToC(val)};")
# ── Assign ── # ── Assign ──
of hAssign: of hAssign:
let target = lowerExpr(ctx, node.assignTarget)
let value = lowerExpr(ctx, node.assignValue) let value = lowerExpr(ctx, node.assignValue)
case node.assignOp case node.assignOp
of tkAssign: of tkAssign:
b.emitMov(target, value) case node.assignTarget.kind
of hFieldPtr:
let lval = buildLval(ctx, node.assignTarget)
b.emitRawC(&"{lval} = {lirValToC(value)};")
of hArrowField:
let lval = buildLval(ctx, node.assignTarget)
b.emitRawC(&"{lval} = {lirValToC(value)};")
of hIndexPtr:
let base = lowerExpr(ctx, node.assignTarget.indexPtrBase)
let idx = lowerExpr(ctx, node.assignTarget.indexPtrIndex)
b.emit(LirInstr(kind: lirStore, src: value, src2: base, dst: idx))
of hLoad:
if node.assignTarget.loadPtr != nil:
let ptrNode = node.assignTarget.loadPtr
case ptrNode.kind
of hIndexPtr:
let base = lowerExpr(ctx, ptrNode.indexPtrBase)
let idx = lowerExpr(ctx, ptrNode.indexPtrIndex)
b.emit(LirInstr(kind: lirStore, src: value, src2: base, dst: idx))
of hFieldPtr:
let lval = buildLval(ctx, ptrNode)
b.emitRawC(&"{lval} = {lirValToC(value)};")
of hArrowField:
let lval = buildLval(ctx, ptrNode)
b.emitRawC(&"{lval} = {lirValToC(value)};")
else:
let ptrVal = lowerExpr(ctx, ptrNode)
let valCType = hirTypeToC(ctx, node.assignValue)
b.emitRawC(&"*({valCType}*){lirValToC(ptrVal)} = {lirValToC(value)};")
else:
let target = lowerExpr(ctx, node.assignTarget)
b.emitMov(target, value)
else:
let target = lowerExpr(ctx, node.assignTarget)
b.emitMov(target, value)
of tkPlusAssign: of tkPlusAssign:
let target = lowerExpr(ctx, node.assignTarget)
let t = b.freshTemp() let t = b.freshTemp()
b.emitBinOp(lirAdd, t, target, value) b.emitBinOp(lirAdd, t, target, value)
b.emit(LirInstr(kind: lirStore, src: t, src2: target)) b.emit(LirInstr(kind: lirStore, src: t, src2: target))
of tkMinusAssign: of tkMinusAssign:
let target = lowerExpr(ctx, node.assignTarget)
let t = b.freshTemp() let t = b.freshTemp()
b.emitBinOp(lirSub, t, target, value) b.emitBinOp(lirSub, t, target, value)
b.emit(LirInstr(kind: lirStore, src: t, src2: target)) b.emit(LirInstr(kind: lirStore, src: t, src2: target))
else: else:
let target = lowerExpr(ctx, node.assignTarget)
b.emitMov(target, value) b.emitMov(target, value)
# ── Call statement (void return) ── # ── Call statement (void return) ──
+5 -1
View File
@@ -239,9 +239,13 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
// Try to resolve return type from function declaration // Try to resolve return type from function declaration
if expr.child1.kind == ekIdent { if expr.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue); let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue);
if sym.kind == skFunc && sym.decl != null as *Decl && sym.decl.retType != null as *TypeExpr { if sym.kind == skFunc {
if sym.decl != null as *Decl {
if sym.decl.retType != null as *TypeExpr {
return Sema_ResolveType(sema, sym.decl.retType); return Sema_ResolveType(sema, sym.decl.retType);
} }
}
}
} }
return tyUnknown; return tyUnknown;
} }