feat: match guards, HOF inference, ownership C.2/C.3, LSP sema hover
Sessions 18–23 quality work: - B.3c match arm guards + sequential found-flag lower (bootstrap + selfhost) - Generic HOF type inference (Array/Iter map/filter/fold without type args) - Pattern binding shadowing via unique C locals (__pN_src) - Ownership C.2 exclusive &mut data-flow + C.4 goldens; *p= store-through fix - Ownership C.3 auto-drop on early return/branches: scoped defers, move-on-return, Drop monomorphization, materialize return before Drop - LSP 0.3.0: hover from real sema types - Examples and QUALITY_PLAN session log; selfhost-loop identical
This commit is contained in:
@@ -3,7 +3,7 @@ SRC := bootstrap/main.nim
|
|||||||
OUT := buxc
|
OUT := buxc
|
||||||
BUILD_DIR := build
|
BUILD_DIR := build
|
||||||
|
|
||||||
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic struct_tuple_pat match_block nested_patterns
|
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ownership_checked drop_early_return ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic generic_infer_hof struct_tuple_pat match_block nested_patterns match_guards pattern_shadow
|
||||||
|
|
||||||
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden test-errors selfhost-loop lsp
|
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden test-errors selfhost-loop lsp
|
||||||
|
|
||||||
@@ -143,5 +143,5 @@ selfhost-loop: build
|
|||||||
lsp: tools/bux-lsp
|
lsp: tools/bux-lsp
|
||||||
@echo "LSP server ready at tools/bux-lsp"
|
@echo "LSP server ready at tools/bux-lsp"
|
||||||
|
|
||||||
tools/bux-lsp: tools/lsp_server.nim
|
tools/bux-lsp: tools/lsp_server.nim bootstrap/*.nim
|
||||||
cd tools && $(NIM) c -o:bux-lsp lsp_server.nim
|
cd tools && $(NIM) c -d:release --opt:size --path:../bootstrap -o:bux-lsp lsp_server.nim
|
||||||
|
|||||||
+5
-1
@@ -180,6 +180,8 @@ proc underlineLength(lineText: string, col: uint32, message: string): int =
|
|||||||
proc hintForMessage(msg: string): string =
|
proc hintForMessage(msg: string): string =
|
||||||
## Actionable help text for common compiler errors.
|
## Actionable help text for common compiler errors.
|
||||||
let m = msg.toLowerAscii()
|
let m = msg.toLowerAscii()
|
||||||
|
if "while it is mutably borrowed" in m:
|
||||||
|
return "only one active '&mut' borrow is allowed at a time; end the borrow before reuse"
|
||||||
if "cannot assign" in m:
|
if "cannot assign" in m:
|
||||||
return "ensure the right-hand side type matches the left-hand side"
|
return "ensure the right-hand side type matches the left-hand side"
|
||||||
if "undeclared identifier" in m:
|
if "undeclared identifier" in m:
|
||||||
@@ -193,7 +195,9 @@ proc hintForMessage(msg: string): string =
|
|||||||
if "shared reference" in m or "checked function" in m:
|
if "shared reference" in m or "checked function" in m:
|
||||||
return "use '&mut T' for mutation, or drop @[Checked] for unchecked code"
|
return "use '&mut T' for mutation, or drop @[Checked] for unchecked code"
|
||||||
if "double mutable borrow" in m or "already mutably borrowed" in m:
|
if "double mutable borrow" in m or "already mutably borrowed" in m:
|
||||||
return "only one active '&mut' borrow is allowed at a time"
|
return "only one active '&mut' borrow is allowed at a time; end the borrow before reuse"
|
||||||
|
if "shared-borrow" in m or "shared-borrowed" in m:
|
||||||
|
return "exclusive '&mut' and shared '&' cannot overlap on the same variable"
|
||||||
if "expected expression" in m:
|
if "expected expression" in m:
|
||||||
return "the previous statement may be incomplete (missing value or ';')"
|
return "the previous statement may be incomplete (missing value or ';')"
|
||||||
if "expected type" in m:
|
if "expected type" in m:
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ type
|
|||||||
HirMatchArm* = object
|
HirMatchArm* = object
|
||||||
pattern*: Pattern
|
pattern*: Pattern
|
||||||
body*: HirNode
|
body*: HirNode
|
||||||
|
guard*: HirNode ## optional: lowered `if cond` from `p if cond => body`
|
||||||
|
|
||||||
HirFunc* = object
|
HirFunc* = object
|
||||||
name*: string
|
name*: string
|
||||||
|
|||||||
+260
-96
@@ -31,13 +31,83 @@ type
|
|||||||
## All func types that need BuxFn_* typedefs (including locals)
|
## All func types that need BuxFn_* typedefs (including locals)
|
||||||
seenFatTypes*: seq[Type]
|
seenFatTypes*: seq[Type]
|
||||||
## Pattern-binding names already alloca'd in the current function
|
## Pattern-binding names already alloca'd in the current function
|
||||||
## (avoids `int v;` twice when two matches bind the same name)
|
## (legacy; unique mangled names are preferred for shadowing safety)
|
||||||
patternBoundNames*: HashSet[string]
|
patternBoundNames*: HashSet[string]
|
||||||
|
## Active renames: source pattern name → unique C local (for shadowing)
|
||||||
|
patternRenames*: Table[string, string]
|
||||||
|
|
||||||
proc freshName(ctx: var LowerCtx): string =
|
proc freshName(ctx: var LowerCtx): string =
|
||||||
inc ctx.varCounter
|
inc ctx.varCounter
|
||||||
result = "__tmp_" & $ctx.varCounter
|
result = "__tmp_" & $ctx.varCounter
|
||||||
|
|
||||||
|
proc freshPatName(ctx: var LowerCtx, src: string): string =
|
||||||
|
## Unique C name for a pattern binding (allows shadowing outer lets / nested matches).
|
||||||
|
inc ctx.varCounter
|
||||||
|
let safe = if src.len > 0 and src != "_": src else: "x"
|
||||||
|
result = "__p" & $ctx.varCounter & "_" & safe
|
||||||
|
|
||||||
|
proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs: seq[TypeExpr]): string
|
||||||
|
|
||||||
|
proc namedTypeArg(name: string): TypeExpr =
|
||||||
|
TypeExpr(kind: tekNamed, typeName: name)
|
||||||
|
|
||||||
|
proc ensureDropMono(ctx: var LowerCtx, dropBase: string, freeBase: string, typeArgs: seq[TypeExpr]) =
|
||||||
|
## Monomorphize Free (if any) then Drop so the C linker finds them.
|
||||||
|
if freeBase.len > 0:
|
||||||
|
discard ctx.generateMethodInstance(freeBase, typeArgs)
|
||||||
|
discard ctx.generateMethodInstance(dropBase, typeArgs)
|
||||||
|
|
||||||
|
proc dropTargetsVar(n: HirNode, name: string): bool =
|
||||||
|
## True if n is Type_Drop(&name) / collection Drop of that local.
|
||||||
|
if n == nil or name.len == 0: return false
|
||||||
|
if n.kind == hCall and n.callArgs.len >= 1:
|
||||||
|
let a = n.callArgs[0]
|
||||||
|
if a != nil and a.kind == hUnary and a.unaryOp == tkAmp and
|
||||||
|
a.unaryOperand != nil and a.unaryOperand.kind == hVar:
|
||||||
|
return a.unaryOperand.varName == name
|
||||||
|
return false
|
||||||
|
|
||||||
|
proc autoDropFuncName(ctx: var LowerCtx, ty: Type): string =
|
||||||
|
## Return `Type_Drop` if this type should be auto-dropped, else "".
|
||||||
|
## Also monomorphizes generic Drop/Free helpers for stdlib collections.
|
||||||
|
if ty == nil: return ""
|
||||||
|
var typeName = ""
|
||||||
|
if ty.kind == tkNamed:
|
||||||
|
typeName = ty.name
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
# User type with @[Drop]
|
||||||
|
let sym = ctx.globalScope.lookup(typeName)
|
||||||
|
if sym != nil and sym.decl != nil and sym.decl.kind == dkStruct:
|
||||||
|
if "Drop" in sym.decl.declAttrs:
|
||||||
|
return typeName & "_Drop"
|
||||||
|
# Explicit Type_Drop function exists (extend … for Drop)
|
||||||
|
let dropSym = ctx.globalScope.lookup(typeName & "_Drop")
|
||||||
|
if dropSym != nil and dropSym.kind == skFunc:
|
||||||
|
return typeName & "_Drop"
|
||||||
|
# Stdlib mangled collections: Array_int → Array_Drop_int
|
||||||
|
if typeName.startsWith("Array_"):
|
||||||
|
let elem = typeName[6 .. ^1]
|
||||||
|
ctx.ensureDropMono("Array_Drop", "Array_Free", @[namedTypeArg(elem)])
|
||||||
|
return "Array_Drop_" & elem
|
||||||
|
if typeName.startsWith("Map_"):
|
||||||
|
let rest = typeName[4 .. ^1]
|
||||||
|
let us = rest.find('_')
|
||||||
|
if us > 0:
|
||||||
|
let k = rest[0 ..< us]
|
||||||
|
let v = rest[us+1 .. ^1]
|
||||||
|
ctx.ensureDropMono("Map_Drop", "Map_Free", @[namedTypeArg(k), namedTypeArg(v)])
|
||||||
|
return "Map_Drop_" & rest
|
||||||
|
if typeName.startsWith("Set_"):
|
||||||
|
let elem = typeName[4 .. ^1]
|
||||||
|
ctx.ensureDropMono("Set_Drop", "Set_Free", @[namedTypeArg(elem)])
|
||||||
|
return "Set_Drop_" & elem
|
||||||
|
if typeName.startsWith("Channel_"):
|
||||||
|
let elem = typeName[8 .. ^1]
|
||||||
|
ctx.ensureDropMono("Channel_Drop", "Channel_Free", @[namedTypeArg(elem)])
|
||||||
|
return "Channel_Drop_" & elem
|
||||||
|
return ""
|
||||||
|
|
||||||
proc freshTryVar(ctx: var LowerCtx): string =
|
proc freshTryVar(ctx: var LowerCtx): string =
|
||||||
inc ctx.tryCounter
|
inc ctx.tryCounter
|
||||||
result = "__try_" & $ctx.tryCounter
|
result = "__try_" & $ctx.tryCounter
|
||||||
@@ -73,9 +143,6 @@ proc patternLiteralNode(pat: Pattern, loc: SourceLocation): HirNode =
|
|||||||
return nil
|
return nil
|
||||||
return hirLit(pat.patLit, litTokenType(pat.patLit), loc)
|
return hirLit(pat.patLit, litTokenType(pat.patLit), loc)
|
||||||
|
|
||||||
proc matchAlwaysTrue(loc: SourceLocation): HirNode =
|
|
||||||
hirLit(Token(kind: tkBoolLiteral, text: "true", loc: loc), makeBool(), loc)
|
|
||||||
|
|
||||||
proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type
|
proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type
|
||||||
|
|
||||||
proc matchPatternCond(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
proc matchPatternCond(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
||||||
@@ -123,18 +190,33 @@ proc matchPatternCond(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
|||||||
# Single-segment enum path — always-true fallback
|
# Single-segment enum path — always-true fallback
|
||||||
return nil
|
return nil
|
||||||
of pkGuarded:
|
of pkGuarded:
|
||||||
# Guard: inner pattern AND guard expression (lowered later if needed)
|
# Condition is only the inner pattern; guard is applied after bindings in lowerMatch.
|
||||||
# For now only support always-true inner + guard as bool expr via lowerExpr path.
|
return matchPatternCond(ctx, subject, pattern.patGuardedInner, subjectEnumName, subjectHasData, loc)
|
||||||
# Guards are not yet fully lowered here (require expr lowering of guard).
|
|
||||||
return nil
|
|
||||||
else:
|
else:
|
||||||
# Struct/tuple patterns: not yet fully lowered — always-true
|
# Struct/tuple patterns: not yet fully lowered — always-true
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
# lowerMatch calls lowerExpr for arm bodies after emitting bindings
|
||||||
|
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode
|
||||||
|
|
||||||
|
proc bindPatLocal(ctx: var LowerCtx, srcName: string, ty: Type, subject: HirNode,
|
||||||
|
loc: SourceLocation): seq[HirNode] =
|
||||||
|
## Allocate a unique C local for a pattern binding and map source name → C name.
|
||||||
|
result = @[]
|
||||||
|
if srcName.len == 0 or srcName == "_":
|
||||||
|
return
|
||||||
|
let cName = ctx.freshPatName(srcName)
|
||||||
|
ctx.patternRenames[srcName] = cName
|
||||||
|
ctx.patternBoundNames.incl(srcName)
|
||||||
|
result.add(hirAlloca(cName, ty, loc))
|
||||||
|
result.add(hirStore(hirVar(cName, ty, loc), subject, loc))
|
||||||
|
|
||||||
proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
||||||
subjectEnumName: string, subjectHasData: bool,
|
subjectEnumName: string, subjectHasData: bool,
|
||||||
loc: SourceLocation): seq[HirNode] =
|
loc: SourceLocation): seq[HirNode] =
|
||||||
## Emit alloca+store for identifiers bound by a match pattern.
|
## Emit alloca+store for identifiers bound by a match pattern.
|
||||||
|
## Each binding gets a unique C name (`__pN_src`) so nested matches and
|
||||||
|
## outer `let` can share source names without C redeclaration / use-before-decl.
|
||||||
## Enum payload: `Option::Some(value)` → `value = subject.data.Some_0`
|
## Enum payload: `Option::Some(value)` → `value = subject.data.Some_0`
|
||||||
## Ident catch-all: `x` → `x = subject`
|
## Ident catch-all: `x` → `x = subject`
|
||||||
result = @[]
|
result = @[]
|
||||||
@@ -142,10 +224,7 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
|||||||
case pattern.kind
|
case pattern.kind
|
||||||
of pkIdent:
|
of pkIdent:
|
||||||
let ty = if subject.typ != nil: subject.typ else: makeUnknown()
|
let ty = if subject.typ != nil: subject.typ else: makeUnknown()
|
||||||
if pattern.patIdent notin ctx.patternBoundNames:
|
result.add(ctx.bindPatLocal(pattern.patIdent, ty, subject, loc))
|
||||||
result.add(hirAlloca(pattern.patIdent, ty, loc))
|
|
||||||
ctx.patternBoundNames.incl(pattern.patIdent)
|
|
||||||
result.add(hirStore(hirVar(pattern.patIdent, ty, loc), subject, loc))
|
|
||||||
of pkEnum:
|
of pkEnum:
|
||||||
if not subjectHasData:
|
if not subjectHasData:
|
||||||
return
|
return
|
||||||
@@ -195,10 +274,7 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
|||||||
typ: makePointer(fieldTy), loc: loc)
|
typ: makePointer(fieldTy), loc: loc)
|
||||||
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
|
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
|
||||||
if arg.kind == pkIdent:
|
if arg.kind == pkIdent:
|
||||||
if arg.patIdent notin ctx.patternBoundNames:
|
result.add(ctx.bindPatLocal(arg.patIdent, fieldTy, fieldLoad, loc))
|
||||||
result.add(hirAlloca(arg.patIdent, fieldTy, loc))
|
|
||||||
ctx.patternBoundNames.incl(arg.patIdent)
|
|
||||||
result.add(hirStore(hirVar(arg.patIdent, fieldTy, loc), fieldLoad, loc))
|
|
||||||
else:
|
else:
|
||||||
# Nested: Option::Some((a, b)), Pair::Two(Point { x, y })
|
# Nested: Option::Some((a, b)), Pair::Two(Point { x, y })
|
||||||
result.add(ctx.matchPatternBindings(fieldLoad, arg, subjectEnumName, subjectHasData, loc))
|
result.add(ctx.matchPatternBindings(fieldLoad, arg, subjectEnumName, subjectHasData, loc))
|
||||||
@@ -220,10 +296,7 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
|||||||
typ: makePointer(fieldTy), loc: loc)
|
typ: makePointer(fieldTy), loc: loc)
|
||||||
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
|
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
|
||||||
if nf.pattern.kind == pkIdent:
|
if nf.pattern.kind == pkIdent:
|
||||||
if nf.pattern.patIdent notin ctx.patternBoundNames:
|
result.add(ctx.bindPatLocal(nf.pattern.patIdent, fieldTy, fieldLoad, loc))
|
||||||
result.add(hirAlloca(nf.pattern.patIdent, fieldTy, loc))
|
|
||||||
ctx.patternBoundNames.incl(nf.pattern.patIdent)
|
|
||||||
result.add(hirStore(hirVar(nf.pattern.patIdent, fieldTy, loc), fieldLoad, loc))
|
|
||||||
else:
|
else:
|
||||||
result.add(ctx.matchPatternBindings(fieldLoad, nf.pattern, subjectEnumName, subjectHasData, loc))
|
result.add(ctx.matchPatternBindings(fieldLoad, nf.pattern, subjectEnumName, subjectHasData, loc))
|
||||||
of pkGuarded:
|
of pkGuarded:
|
||||||
@@ -241,10 +314,7 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
|||||||
typ: makePointer(fieldTy), loc: loc)
|
typ: makePointer(fieldTy), loc: loc)
|
||||||
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
|
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
|
||||||
if elem.kind == pkIdent:
|
if elem.kind == pkIdent:
|
||||||
if elem.patIdent notin ctx.patternBoundNames:
|
result.add(ctx.bindPatLocal(elem.patIdent, fieldTy, fieldLoad, loc))
|
||||||
result.add(hirAlloca(elem.patIdent, fieldTy, loc))
|
|
||||||
ctx.patternBoundNames.incl(elem.patIdent)
|
|
||||||
result.add(hirStore(hirVar(elem.patIdent, fieldTy, loc), fieldLoad, loc))
|
|
||||||
else:
|
else:
|
||||||
# Nested patterns: recurse with field as subject
|
# Nested patterns: recurse with field as subject
|
||||||
result.add(ctx.matchPatternBindings(fieldLoad, elem, subjectEnumName, subjectHasData, loc))
|
result.add(ctx.matchPatternBindings(fieldLoad, elem, subjectEnumName, subjectHasData, loc))
|
||||||
@@ -269,68 +339,114 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
|||||||
typ: makePointer(fieldTy), loc: loc)
|
typ: makePointer(fieldTy), loc: loc)
|
||||||
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
|
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
|
||||||
if fpat.kind == pkIdent:
|
if fpat.kind == pkIdent:
|
||||||
if fpat.patIdent notin ctx.patternBoundNames:
|
result.add(ctx.bindPatLocal(fpat.patIdent, fieldTy, fieldLoad, loc))
|
||||||
result.add(hirAlloca(fpat.patIdent, fieldTy, loc))
|
|
||||||
ctx.patternBoundNames.incl(fpat.patIdent)
|
|
||||||
result.add(hirStore(hirVar(fpat.patIdent, fieldTy, loc), fieldLoad, loc))
|
|
||||||
else:
|
else:
|
||||||
result.add(ctx.matchPatternBindings(fieldLoad, fpat, subjectEnumName, subjectHasData, loc))
|
result.add(ctx.matchPatternBindings(fieldLoad, fpat, subjectEnumName, subjectHasData, loc))
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ: Type, loc: SourceLocation): HirNode =
|
proc lowerMatch(ctx: var LowerCtx, subject: HirNode, astArms: seq[MatchArm], typ: Type, loc: SourceLocation): HirNode =
|
||||||
## Lower match expression to a block with if-else chain.
|
## Lower match expression to sequential ifs with a `found` flag.
|
||||||
## Supports: enum tags + payload bindings, integer/bool/char/string literals,
|
## Supports: enum tags + payload bindings, integer/bool/char/string literals,
|
||||||
## ranges, wildcard/ident catch-all.
|
## ranges, wildcard/ident catch-all, and `p if guard` arms.
|
||||||
|
##
|
||||||
|
## Each arm:
|
||||||
|
## 1. emit unique pattern bindings (sets patternRenames)
|
||||||
|
## 2. lower guard + body (idents use renames)
|
||||||
|
## 3. restore renames
|
||||||
|
## if (!found) { if (cond) { binds; if (guard) { result=body; found=true } } }
|
||||||
let hasResult = typ != nil and typ.kind != tkVoid and typ.kind != tkUnknown
|
let hasResult = typ != nil and typ.kind != tkVoid and typ.kind != tkUnknown
|
||||||
let resultName = ctx.freshName()
|
let resultName = ctx.freshName()
|
||||||
|
let foundName = ctx.freshName()
|
||||||
var stmts: seq[HirNode] = @[]
|
var stmts: seq[HirNode] = @[]
|
||||||
|
|
||||||
if hasResult:
|
if hasResult:
|
||||||
stmts.add(hirAlloca(resultName, typ, loc))
|
stmts.add(hirAlloca(resultName, typ, loc))
|
||||||
|
stmts.add(hirAlloca(foundName, makeBool(), loc))
|
||||||
|
stmts.add(hirStore(hirVar(foundName, makeBool(), loc),
|
||||||
|
hirLit(Token(kind: tkBoolLiteral, text: "false", loc: loc), makeBool(), loc), loc))
|
||||||
|
|
||||||
# Determine whether the matched enum has data variants (needs .tag access).
|
|
||||||
var subjectEnumName = ""
|
var subjectEnumName = ""
|
||||||
var subjectHasData = false
|
var subjectHasData = false
|
||||||
if subject.typ != nil and subject.typ.kind == tkNamed:
|
if subject.typ != nil and subject.typ.kind == tkNamed:
|
||||||
subjectEnumName = subject.typ.name
|
subjectEnumName = subject.typ.name
|
||||||
subjectHasData = ctx.enumHasDataVariants(subjectEnumName)
|
subjectHasData = ctx.enumHasDataVariants(subjectEnumName)
|
||||||
|
|
||||||
proc makeArmBlock(body: HirNode, bindStmts: seq[HirNode]): HirNode =
|
for arm in astArms:
|
||||||
var armStmts: seq[HirNode] = bindStmts
|
# Snapshot renames so this arm's bindings don't leak to later arms
|
||||||
|
let savedRenames = ctx.patternRenames
|
||||||
|
|
||||||
|
var innerPat = arm.pattern
|
||||||
|
if arm.pattern != nil and arm.pattern.kind == pkGuarded:
|
||||||
|
innerPat = arm.pattern.patGuardedInner
|
||||||
|
|
||||||
|
# Register bind types for resolveExprType during body lower
|
||||||
|
if innerPat != nil and innerPat.kind == pkEnum and subjectHasData:
|
||||||
|
var enumName = ""
|
||||||
|
var variantName = ""
|
||||||
|
if innerPat.patEnumPath.len >= 2:
|
||||||
|
enumName = innerPat.patEnumPath[0]
|
||||||
|
variantName = innerPat.patEnumPath[^1]
|
||||||
|
elif innerPat.patEnumPath.len == 1:
|
||||||
|
variantName = innerPat.patEnumPath[0]
|
||||||
|
enumName = subjectEnumName
|
||||||
|
var fieldTypes: seq[Type] = @[]
|
||||||
|
let enumSym = ctx.globalScope.lookup(enumName)
|
||||||
|
if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum:
|
||||||
|
for v in enumSym.decl.declEnumVariants:
|
||||||
|
if v.name == variantName:
|
||||||
|
for f in v.fields:
|
||||||
|
fieldTypes.add(ctx.resolveTypeExpr(f))
|
||||||
|
break
|
||||||
|
for i, arg in innerPat.patEnumArgs:
|
||||||
|
if arg != nil and arg.kind == pkIdent:
|
||||||
|
let ft = if i < fieldTypes.len: fieldTypes[i] else: makeInt()
|
||||||
|
ctx.varTypeExprs[arg.patIdent] = typeToTypeExpr(ft)
|
||||||
|
elif innerPat != nil and innerPat.kind == pkIdent:
|
||||||
|
let ty = if subject.typ != nil: subject.typ else: makeUnknown()
|
||||||
|
ctx.varTypeExprs[innerPat.patIdent] = typeToTypeExpr(ty)
|
||||||
|
|
||||||
|
# Bindings BEFORE body so (1) renames active (2) alloca precedes use in C
|
||||||
|
let binds = matchPatternBindings(ctx, subject, innerPat, subjectEnumName, subjectHasData, loc)
|
||||||
|
|
||||||
|
var guardHir: HirNode = nil
|
||||||
|
if arm.pattern != nil and arm.pattern.kind == pkGuarded and arm.pattern.patGuardedExpr != nil:
|
||||||
|
guardHir = ctx.lowerExpr(arm.pattern.patGuardedExpr)
|
||||||
|
let bodyHir = ctx.lowerExpr(arm.body)
|
||||||
|
|
||||||
|
# Pop this arm's renames (nested matches already restored themselves)
|
||||||
|
ctx.patternRenames = savedRenames
|
||||||
|
|
||||||
|
var successStmts: seq[HirNode] = @[]
|
||||||
if hasResult:
|
if hasResult:
|
||||||
armStmts.add(hirStore(hirVar(resultName, typ, loc), body, loc))
|
successStmts.add(hirStore(hirVar(resultName, typ, loc), bodyHir, loc))
|
||||||
elif body != nil:
|
elif bodyHir != nil:
|
||||||
# Void match: evaluate body for side effects
|
successStmts.add(bodyHir)
|
||||||
armStmts.add(body)
|
successStmts.add(hirStore(hirVar(foundName, makeBool(), loc),
|
||||||
return hirBlock(armStmts, nil, makeVoid(), loc)
|
hirLit(Token(kind: tkBoolLiteral, text: "true", loc: loc), makeBool(), loc), loc))
|
||||||
|
let successBlock = hirBlock(successStmts, nil, makeVoid(), loc)
|
||||||
|
|
||||||
# Build if-else chain from arms (last arm is the outermost else)
|
var afterBinds: HirNode
|
||||||
var ifChain: HirNode = nil
|
if guardHir != nil:
|
||||||
|
afterBinds = HirNode(kind: hIf, ifCond: guardHir, ifThen: successBlock, ifElse: nil,
|
||||||
for i in countdown(arms.len - 1, 0):
|
|
||||||
let arm = arms[i]
|
|
||||||
let binds = matchPatternBindings(ctx, subject, arm.pattern, subjectEnumName, subjectHasData, loc)
|
|
||||||
let armBlock = makeArmBlock(arm.body, binds)
|
|
||||||
let cond = matchPatternCond(ctx, subject, arm.pattern, subjectEnumName, subjectHasData, loc)
|
|
||||||
|
|
||||||
if cond == nil:
|
|
||||||
# Always-true arm (wildcard / incomplete pattern)
|
|
||||||
if ifChain == nil:
|
|
||||||
ifChain = armBlock
|
|
||||||
else:
|
|
||||||
ifChain = HirNode(kind: hIf, ifCond: matchAlwaysTrue(loc), ifThen: armBlock,
|
|
||||||
ifElse: ifChain, typ: makeVoid(), loc: loc)
|
|
||||||
else:
|
|
||||||
if ifChain == nil:
|
|
||||||
ifChain = HirNode(kind: hIf, ifCond: cond, ifThen: armBlock, ifElse: nil,
|
|
||||||
typ: makeVoid(), loc: loc)
|
typ: makeVoid(), loc: loc)
|
||||||
else:
|
else:
|
||||||
ifChain = HirNode(kind: hIf, ifCond: cond, ifThen: armBlock, ifElse: ifChain,
|
afterBinds = successBlock
|
||||||
|
|
||||||
|
var armInnerStmts = binds
|
||||||
|
armInnerStmts.add(afterBinds)
|
||||||
|
let armInner = hirBlock(armInnerStmts, nil, makeVoid(), loc)
|
||||||
|
|
||||||
|
let cond = matchPatternCond(ctx, subject, innerPat, subjectEnumName, subjectHasData, loc)
|
||||||
|
let armBody = if cond == nil: armInner
|
||||||
|
else: HirNode(kind: hIf, ifCond: cond, ifThen: armInner, ifElse: nil,
|
||||||
typ: makeVoid(), loc: loc)
|
typ: makeVoid(), loc: loc)
|
||||||
|
|
||||||
if ifChain != nil:
|
let notFound = HirNode(kind: hUnary, unaryOp: tkBang,
|
||||||
stmts.add(ifChain)
|
unaryOperand: hirVar(foundName, makeBool(), loc),
|
||||||
|
typ: makeBool(), loc: loc)
|
||||||
|
stmts.add(HirNode(kind: hIf, ifCond: notFound, ifThen: armBody, ifElse: nil,
|
||||||
|
typ: makeVoid(), loc: loc))
|
||||||
|
|
||||||
if hasResult:
|
if hasResult:
|
||||||
return hirBlock(stmts, hirVar(resultName, typ, loc), typ, loc)
|
return hirBlock(stmts, hirVar(resultName, typ, loc), typ, loc)
|
||||||
@@ -357,6 +473,7 @@ proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
|
|||||||
result.funcAdapterSigs = initTable[string, Type]()
|
result.funcAdapterSigs = initTable[string, Type]()
|
||||||
result.seenFatTypes = @[]
|
result.seenFatTypes = @[]
|
||||||
result.patternBoundNames = initHashSet[string]()
|
result.patternBoundNames = initHashSet[string]()
|
||||||
|
result.patternRenames = initTable[string, string]()
|
||||||
|
|
||||||
proc sanitizeFatPart(s: string): string =
|
proc sanitizeFatPart(s: string): string =
|
||||||
result = s.replace("const char*", "cstr").replace("unsigned int", "uint")
|
result = s.replace("const char*", "cstr").replace("unsigned int", "uint")
|
||||||
@@ -544,8 +661,7 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
|
|||||||
return makeFunc(params, ret)
|
return makeFunc(params, ret)
|
||||||
else: return makeUnknown()
|
else: return makeUnknown()
|
||||||
|
|
||||||
# Forward declarations
|
# Forward declarations (lowerExpr already declared above for lowerMatch)
|
||||||
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode
|
|
||||||
proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode
|
proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode
|
||||||
proc lowerBlock(ctx: var LowerCtx, blk: Block, asExpr = false): HirNode
|
proc lowerBlock(ctx: var LowerCtx, blk: Block, asExpr = false): HirNode
|
||||||
proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc
|
proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc
|
||||||
@@ -854,8 +970,6 @@ proc getCollectionElementTypeExpr(ctx: var LowerCtx, expr: Expr): TypeExpr =
|
|||||||
return typeToTypeExpr(concreteArgs[0])
|
return typeToTypeExpr(concreteArgs[0])
|
||||||
return TypeExpr(kind: tekNamed, typeName: "unknown")
|
return TypeExpr(kind: tekNamed, typeName: "unknown")
|
||||||
|
|
||||||
proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs: seq[TypeExpr]): string
|
|
||||||
|
|
||||||
proc lowerExprWithDynRefCoerce(ctx: var LowerCtx, arg: Expr, expectedType: Type): HirNode =
|
proc lowerExprWithDynRefCoerce(ctx: var LowerCtx, arg: Expr, expectedType: Type): HirNode =
|
||||||
## Lower an expression, coercing &Concrete to &dyn Trait if needed.
|
## Lower an expression, coercing &Concrete to &dyn Trait if needed.
|
||||||
let lowered = ctx.lowerExpr(arg)
|
let lowered = ctx.lowerExpr(arg)
|
||||||
@@ -997,6 +1111,10 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
|
|
||||||
of ekIdent:
|
of ekIdent:
|
||||||
let name = expr.exprIdent
|
let name = expr.exprIdent
|
||||||
|
# Pattern binding rename: source name → unique C local (`__pN_v`)
|
||||||
|
if ctx.patternRenames.hasKey(name):
|
||||||
|
let cName = ctx.patternRenames[name]
|
||||||
|
return hirVar(cName, typ, loc)
|
||||||
# Capture rewriting: if inside closure and ident is captured
|
# Capture rewriting: if inside closure and ident is captured
|
||||||
if ctx.closureDepth > 0 and ctx.currentClosureExpr != nil and ctx.envInstanceName != "":
|
if ctx.closureDepth > 0 and ctx.currentClosureExpr != nil and ctx.envInstanceName != "":
|
||||||
let idx = ctx.currentClosureExpr.captureNames.find(name)
|
let idx = ctx.currentClosureExpr.captureNames.find(name)
|
||||||
@@ -1286,6 +1404,15 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
args.add(ctx.lowerExpr(idxExpr))
|
args.add(ctx.lowerExpr(idxExpr))
|
||||||
args.add(ctx.lowerExpr(expr.exprAssignValue))
|
args.add(ctx.lowerExpr(expr.exprAssignValue))
|
||||||
return hirCall(calleeName, args, makeVoid(), loc)
|
return hirCall(calleeName, args, makeVoid(), loc)
|
||||||
|
# `*p = value` must store through the pointer, not assign to a loaded temp.
|
||||||
|
# Represent as hAssign to hLoad(loadPtr=p) so LIR emits `*p = value`.
|
||||||
|
if expr.exprAssignTarget.kind == ekUnary and expr.exprAssignTarget.exprUnaryOp == tkStar:
|
||||||
|
let destPtr = ctx.lowerExpr(expr.exprAssignTarget.exprUnaryOperand)
|
||||||
|
let value = ctx.lowerExpr(expr.exprAssignValue)
|
||||||
|
let loadTarget = HirNode(kind: hLoad, loadPtr: destPtr, typ: typ, loc: loc)
|
||||||
|
return HirNode(kind: hAssign, assignOp: tkAssign,
|
||||||
|
assignTarget: loadTarget, assignValue: value,
|
||||||
|
typ: makeVoid(), loc: loc)
|
||||||
let target = ctx.lowerExpr(expr.exprAssignTarget)
|
let target = ctx.lowerExpr(expr.exprAssignTarget)
|
||||||
let value = ctx.lowerExpr(expr.exprAssignValue)
|
let value = ctx.lowerExpr(expr.exprAssignValue)
|
||||||
return HirNode(kind: hAssign, assignOp: expr.exprAssignOp,
|
return HirNode(kind: hAssign, assignOp: expr.exprAssignOp,
|
||||||
@@ -1473,29 +1600,30 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
|
|
||||||
of ekMatch:
|
of ekMatch:
|
||||||
let subject = ctx.lowerExpr(expr.exprMatchSubject)
|
let subject = ctx.lowerExpr(expr.exprMatchSubject)
|
||||||
var subjectEnumName = ""
|
|
||||||
var subjectHasData = false
|
|
||||||
if subject.typ != nil and subject.typ.kind == tkNamed:
|
|
||||||
subjectEnumName = subject.typ.name
|
|
||||||
subjectHasData = ctx.enumHasDataVariants(subjectEnumName)
|
|
||||||
# Prefer resolved match type; fall back to function return type when arms
|
# Prefer resolved match type; fall back to function return type when arms
|
||||||
# only reference pattern bindings (not yet in varTypeExprs during resolve).
|
# only reference pattern bindings (not yet in varTypeExprs during resolve).
|
||||||
var matchTyp = typ
|
var matchTyp = typ
|
||||||
if matchTyp == nil or matchTyp.kind == tkUnknown:
|
if matchTyp == nil or matchTyp.kind == tkUnknown:
|
||||||
if ctx.currentFuncRetType != nil and ctx.currentFuncRetType.kind notin {tkVoid, tkUnknown}:
|
if ctx.currentFuncRetType != nil and ctx.currentFuncRetType.kind notin {tkVoid, tkUnknown}:
|
||||||
matchTyp = ctx.currentFuncRetType
|
matchTyp = ctx.currentFuncRetType
|
||||||
var arms: seq[HirMatchArm] = @[]
|
# Register bind types early so matchTyp fallback can resolve arm bodies
|
||||||
|
var subjectEnumName = ""
|
||||||
|
var subjectHasData = false
|
||||||
|
if subject.typ != nil and subject.typ.kind == tkNamed:
|
||||||
|
subjectEnumName = subject.typ.name
|
||||||
|
subjectHasData = ctx.enumHasDataVariants(subjectEnumName)
|
||||||
for arm in expr.exprMatchArms:
|
for arm in expr.exprMatchArms:
|
||||||
# Register bind types only (do NOT call matchPatternBindings here — that
|
var bindPat = arm.pattern
|
||||||
# would mark names as already alloca'd and lowerMatch would skip them).
|
if bindPat != nil and bindPat.kind == pkGuarded:
|
||||||
if arm.pattern != nil and arm.pattern.kind == pkEnum and subjectHasData:
|
bindPat = bindPat.patGuardedInner
|
||||||
|
if bindPat != nil and bindPat.kind == pkEnum and subjectHasData:
|
||||||
var enumName = ""
|
var enumName = ""
|
||||||
var variantName = ""
|
var variantName = ""
|
||||||
if arm.pattern.patEnumPath.len >= 2:
|
if bindPat.patEnumPath.len >= 2:
|
||||||
enumName = arm.pattern.patEnumPath[0]
|
enumName = bindPat.patEnumPath[0]
|
||||||
variantName = arm.pattern.patEnumPath[^1]
|
variantName = bindPat.patEnumPath[^1]
|
||||||
elif arm.pattern.patEnumPath.len == 1:
|
elif bindPat.patEnumPath.len == 1:
|
||||||
variantName = arm.pattern.patEnumPath[0]
|
variantName = bindPat.patEnumPath[0]
|
||||||
enumName = subjectEnumName
|
enumName = subjectEnumName
|
||||||
var fieldTypes: seq[Type] = @[]
|
var fieldTypes: seq[Type] = @[]
|
||||||
let enumSym = ctx.globalScope.lookup(enumName)
|
let enumSym = ctx.globalScope.lookup(enumName)
|
||||||
@@ -1505,19 +1633,15 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
for f in v.fields:
|
for f in v.fields:
|
||||||
fieldTypes.add(ctx.resolveTypeExpr(f))
|
fieldTypes.add(ctx.resolveTypeExpr(f))
|
||||||
break
|
break
|
||||||
for i, arg in arm.pattern.patEnumArgs:
|
for i, arg in bindPat.patEnumArgs:
|
||||||
if arg != nil and arg.kind == pkIdent:
|
if arg != nil and arg.kind == pkIdent:
|
||||||
let ft = if i < fieldTypes.len: fieldTypes[i] else: makeInt()
|
let ft = if i < fieldTypes.len: fieldTypes[i] else: makeInt()
|
||||||
ctx.varTypeExprs[arg.patIdent] = typeToTypeExpr(ft)
|
ctx.varTypeExprs[arg.patIdent] = typeToTypeExpr(ft)
|
||||||
elif arm.pattern != nil and arm.pattern.kind == pkIdent:
|
elif bindPat != nil and bindPat.kind == pkIdent:
|
||||||
let ty = if subject.typ != nil: subject.typ else: makeUnknown()
|
let ty = if subject.typ != nil: subject.typ else: makeUnknown()
|
||||||
ctx.varTypeExprs[arm.pattern.patIdent] = typeToTypeExpr(ty)
|
ctx.varTypeExprs[bindPat.patIdent] = typeToTypeExpr(ty)
|
||||||
arms.add(HirMatchArm(pattern: arm.pattern, body: ctx.lowerExpr(arm.body)))
|
# Binds + body lower happen inside lowerMatch (unique C names + renames)
|
||||||
# Re-resolve match type now that bindings are registered
|
return lowerMatch(ctx, subject, expr.exprMatchArms, matchTyp, loc)
|
||||||
if matchTyp == nil or matchTyp.kind == tkUnknown:
|
|
||||||
if arms.len > 0 and arms[0].body != nil and arms[0].body.typ != nil:
|
|
||||||
matchTyp = arms[0].body.typ
|
|
||||||
return lowerMatch(ctx, subject, arms, matchTyp, loc)
|
|
||||||
|
|
||||||
of ekSizeOf:
|
of ekSizeOf:
|
||||||
let ty = ctx.resolveTypeExpr(expr.exprSizeOfType)
|
let ty = ctx.resolveTypeExpr(expr.exprSizeOfType)
|
||||||
@@ -1663,6 +1787,13 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
|||||||
if initHir != nil:
|
if initHir != nil:
|
||||||
let store = hirStore(varNode, initHir, loc)
|
let store = hirStore(varNode, initHir, loc)
|
||||||
stmts.add(store)
|
stmts.add(store)
|
||||||
|
# Auto-Drop: @[Drop] types and Array/Map/etc. with TypeName_Drop
|
||||||
|
let dropName = ctx.autoDropFuncName(allocaType)
|
||||||
|
if dropName.len > 0:
|
||||||
|
let addrOf = hirUnary(tkAmp, hirVar(stmt.stmtLetName, allocaType, loc),
|
||||||
|
makePointer(allocaType), loc)
|
||||||
|
let dropCall = hirCall(dropName, @[addrOf], makeVoid(), loc)
|
||||||
|
ctx.deferStmts.add(dropCall)
|
||||||
# Capture filling for closures is done at the ekClosure site (heap env).
|
# Capture filling for closures is done at the ekClosure site (heap env).
|
||||||
return hirBlock(stmts, nil, makeVoid(), loc)
|
return hirBlock(stmts, nil, makeVoid(), loc)
|
||||||
|
|
||||||
@@ -1670,10 +1801,25 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
|||||||
let value = if stmt.stmtReturnValue != nil: ctx.lowerExpr(stmt.stmtReturnValue) else: nil
|
let value = if stmt.stmtReturnValue != nil: ctx.lowerExpr(stmt.stmtReturnValue) else: nil
|
||||||
var stmts = ctx.pendingStmts
|
var stmts = ctx.pendingStmts
|
||||||
ctx.pendingStmts = @[]
|
ctx.pendingStmts = @[]
|
||||||
# Add defers in reverse order (LIFO)
|
# Move-on-return: do not Drop a local that is returned by value.
|
||||||
|
var skipDrop = ""
|
||||||
|
if value != nil and value.kind == hVar:
|
||||||
|
skipDrop = value.varName
|
||||||
|
# Materialize the return value BEFORE drops so `return a.id` is not
|
||||||
|
# use-after-drop (drops are separate stmts; LIR evaluates return expr last).
|
||||||
|
var retVal = value
|
||||||
|
if value != nil and ctx.deferStmts.len > 0:
|
||||||
|
let retTy = if value.typ != nil: value.typ else: makeUnknown()
|
||||||
|
if retTy.kind != tkVoid:
|
||||||
|
let tmp = ctx.freshName()
|
||||||
|
stmts.add(hirAlloca(tmp, retTy, loc))
|
||||||
|
stmts.add(hirStore(hirVar(tmp, retTy, loc), value, loc))
|
||||||
|
retVal = hirVar(tmp, retTy, loc)
|
||||||
|
# Add defers in reverse order (LIFO); snapshot full stack for every return path
|
||||||
for i in countdown(ctx.deferStmts.len - 1, 0):
|
for i in countdown(ctx.deferStmts.len - 1, 0):
|
||||||
|
if not dropTargetsVar(ctx.deferStmts[i], skipDrop):
|
||||||
stmts.add(ctx.deferStmts[i])
|
stmts.add(ctx.deferStmts[i])
|
||||||
stmts.add(hirReturn(value, loc))
|
stmts.add(hirReturn(retVal, loc))
|
||||||
return hirBlock(stmts, nil, makeVoid(), loc)
|
return hirBlock(stmts, nil, makeVoid(), loc)
|
||||||
|
|
||||||
of skIf:
|
of skIf:
|
||||||
@@ -1904,11 +2050,8 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
|||||||
|
|
||||||
of skMatch:
|
of skMatch:
|
||||||
let subject = ctx.lowerExpr(stmt.stmtMatchSubject)
|
let subject = ctx.lowerExpr(stmt.stmtMatchSubject)
|
||||||
var arms: seq[HirMatchArm] = @[]
|
# Statement match: binds + body lower inside lowerMatch (unique C names)
|
||||||
for arm in stmt.stmtMatchArms:
|
return ctx.flushPending(lowerMatch(ctx, subject, stmt.stmtMatchArms, makeVoid(), loc))
|
||||||
arms.add(HirMatchArm(pattern: arm.pattern, body: ctx.lowerExpr(arm.body)))
|
|
||||||
# Statement match: lower to if-else chain (void result)
|
|
||||||
return ctx.flushPending(lowerMatch(ctx, subject, arms, makeVoid(), loc))
|
|
||||||
|
|
||||||
of skSwitch:
|
of skSwitch:
|
||||||
let subject = ctx.lowerExpr(stmt.stmtSwitchExpr)
|
let subject = ctx.lowerExpr(stmt.stmtSwitchExpr)
|
||||||
@@ -1941,7 +2084,13 @@ proc lowerBlock(ctx: var LowerCtx, blk: Block, asExpr = false): HirNode =
|
|||||||
## asExpr=true: block is used as a value (`let x = { ... }`, match arm body).
|
## asExpr=true: block is used as a value (`let x = { ... }`, match arm body).
|
||||||
## Last skExpr becomes the block result. Statement blocks (func body, if/while)
|
## Last skExpr becomes the block result. Statement blocks (func body, if/while)
|
||||||
## keep asExpr=false so trailing void calls stay as statements.
|
## keep asExpr=false so trailing void calls stay as statements.
|
||||||
|
##
|
||||||
|
## Auto-drop / defer scope: locals introduced in this block are dropped at
|
||||||
|
## block exit (LIFO). Nested if/while bodies get their own scope so branch-
|
||||||
|
## local drops do not leak into sibling branches. Early return still injects
|
||||||
|
## the full live stack (see skReturn).
|
||||||
if blk == nil: return nil
|
if blk == nil: return nil
|
||||||
|
let deferBase = ctx.deferStmts.len
|
||||||
var stmts: seq[HirNode] = @[]
|
var stmts: seq[HirNode] = @[]
|
||||||
for s in blk.stmts:
|
for s in blk.stmts:
|
||||||
let hir = ctx.lowerStmt(s)
|
let hir = ctx.lowerStmt(s)
|
||||||
@@ -1966,6 +2115,16 @@ proc lowerBlock(ctx: var LowerCtx, blk: Block, asExpr = false): HirNode =
|
|||||||
let last = stmts[^1]
|
let last = stmts[^1]
|
||||||
stmts[^1] = hirBlock(last.blockStmts, nil, makeVoid(), last.loc)
|
stmts[^1] = hirBlock(last.blockStmts, nil, makeVoid(), last.loc)
|
||||||
expr = last.blockExpr
|
expr = last.blockExpr
|
||||||
|
# Scope exit: Drop locals introduced in this block (not outer ones).
|
||||||
|
# Skip Drop for a local that is the block result (move into expr / caller).
|
||||||
|
var skipDrop = ""
|
||||||
|
if expr != nil and expr.kind == hVar:
|
||||||
|
skipDrop = expr.varName
|
||||||
|
if ctx.deferStmts.len > deferBase:
|
||||||
|
for i in countdown(ctx.deferStmts.len - 1, deferBase):
|
||||||
|
if not dropTargetsVar(ctx.deferStmts[i], skipDrop):
|
||||||
|
stmts.add(ctx.deferStmts[i])
|
||||||
|
ctx.deferStmts.setLen(deferBase)
|
||||||
let typ = if expr != nil and expr.typ != nil: expr.typ else: makeVoid()
|
let typ = if expr != nil and expr.typ != nil: expr.typ else: makeVoid()
|
||||||
return hirBlock(stmts, expr, typ, blk.loc, isScope = true)
|
return hirBlock(stmts, expr, typ, blk.loc, isScope = true)
|
||||||
|
|
||||||
@@ -2008,10 +2167,14 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
|||||||
let oldFuncRetType = ctx.currentFuncRetType
|
let oldFuncRetType = ctx.currentFuncRetType
|
||||||
let oldVarTypeExprs = ctx.varTypeExprs
|
let oldVarTypeExprs = ctx.varTypeExprs
|
||||||
let oldPatternBound = ctx.patternBoundNames
|
let oldPatternBound = ctx.patternBoundNames
|
||||||
|
let oldPatternRenames = ctx.patternRenames
|
||||||
ctx.currentFuncRetType = retType
|
ctx.currentFuncRetType = retType
|
||||||
ctx.currentFuncDecl = decl
|
ctx.currentFuncDecl = decl
|
||||||
ctx.varTypeExprs = initTable[string, TypeExpr]() # Clear local vars for new function
|
ctx.varTypeExprs = initTable[string, TypeExpr]() # Clear local vars for new function
|
||||||
ctx.patternBoundNames = initHashSet[string]()
|
ctx.patternBoundNames = initHashSet[string]()
|
||||||
|
ctx.patternRenames = initTable[string, string]()
|
||||||
|
let oldDefers = ctx.deferStmts
|
||||||
|
ctx.deferStmts = @[]
|
||||||
# Add parameters to varTypeExprs after clearing so they are visible in the body.
|
# Add parameters to varTypeExprs after clearing so they are visible in the body.
|
||||||
for p in funcParams:
|
for p in funcParams:
|
||||||
if p.ptype != nil:
|
if p.ptype != nil:
|
||||||
@@ -2032,12 +2195,13 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
|||||||
if not hasReturn:
|
if not hasReturn:
|
||||||
for i in countdown(ctx.deferStmts.len - 1, 0):
|
for i in countdown(ctx.deferStmts.len - 1, 0):
|
||||||
body.blockStmts.add(ctx.deferStmts[i])
|
body.blockStmts.add(ctx.deferStmts[i])
|
||||||
ctx.deferStmts = @[]
|
ctx.deferStmts = oldDefers
|
||||||
|
|
||||||
ctx.currentFuncDecl = oldFuncDecl
|
ctx.currentFuncDecl = oldFuncDecl
|
||||||
ctx.currentFuncRetType = oldFuncRetType
|
ctx.currentFuncRetType = oldFuncRetType
|
||||||
ctx.varTypeExprs = oldVarTypeExprs
|
ctx.varTypeExprs = oldVarTypeExprs
|
||||||
ctx.patternBoundNames = oldPatternBound
|
ctx.patternBoundNames = oldPatternBound
|
||||||
|
ctx.patternRenames = oldPatternRenames
|
||||||
|
|
||||||
result = HirFunc(name: funcName, params: params, retType: retType,
|
result = HirFunc(name: funcName, params: params, retType: retType,
|
||||||
body: body, isPublic: decl.isPublic)
|
body: body, isPublic: decl.isPublic)
|
||||||
|
|||||||
+28
-8
@@ -63,14 +63,23 @@ proc checkAny(p: Parser, kinds: openArray[TokenKind]): bool =
|
|||||||
return false
|
return false
|
||||||
|
|
||||||
proc isTypeArgListAhead(p: Parser): bool =
|
proc isTypeArgListAhead(p: Parser): bool =
|
||||||
## Lookahead to determine if '<' starts a type argument list.
|
## Lookahead to determine if '<' starts a type argument list (`Foo<int>`).
|
||||||
## Returns true if we can find a matching '>' before EOF, '{', or ';'.
|
## Returns true if we find a matching '>' before tokens that cannot appear
|
||||||
|
## inside type arguments. Must NOT match comparison `x < 0` when a later
|
||||||
|
## `x > 0` exists in the same expression/region (e.g. match arm guards).
|
||||||
if not p.check(tkLt): return false
|
if not p.check(tkLt): return false
|
||||||
var depth = 0
|
var depth = 0
|
||||||
var ahead = 0
|
var ahead = 0
|
||||||
while true:
|
while true:
|
||||||
let kind = p.peek(ahead)
|
let kind = p.peek(ahead)
|
||||||
if kind == tkEndOfFile or kind == tkLBrace or kind == tkSemicolon:
|
# Hard stops: cannot appear inside `<...>` type args
|
||||||
|
if kind in {tkEndOfFile, tkLBrace, tkRBrace, tkSemicolon, tkFatArrow,
|
||||||
|
tkIf, tkElse, tkWhile, tkFor, tkMatch, tkReturn, tkLet, tkVar,
|
||||||
|
tkEq, tkNe, tkLe, tkGe, tkAmpAmp, tkPipePipe, tkAssign}:
|
||||||
|
return false
|
||||||
|
# Literals and comparison/arithmetic mean this is a value expression, not types
|
||||||
|
if kind in {tkIntLiteral, tkFloatLiteral, tkStringLiteral, tkCharLiteral,
|
||||||
|
tkBoolLiteral, tkPlus, tkMinus, tkSlash, tkPercent}:
|
||||||
return false
|
return false
|
||||||
if kind == tkLt:
|
if kind == tkLt:
|
||||||
inc depth
|
inc depth
|
||||||
@@ -170,6 +179,8 @@ type
|
|||||||
targetOs*: string
|
targetOs*: string
|
||||||
checked*: bool ## @[Checked] — enable borrow checking
|
checked*: bool ## @[Checked] — enable borrow checking
|
||||||
shared*: bool ## @[Shared] — mark function as thread-safe
|
shared*: bool ## @[Shared] — mark function as thread-safe
|
||||||
|
drop*: bool ## @[Drop] — auto-call Type_Drop at scope exit
|
||||||
|
release*: bool ## @[Release] — explicit zero-cost (no borrow checks)
|
||||||
|
|
||||||
proc parseAttrs(p: var Parser): ParsedAttrs =
|
proc parseAttrs(p: var Parser): ParsedAttrs =
|
||||||
while p.check(tkAt):
|
while p.check(tkAt):
|
||||||
@@ -180,6 +191,10 @@ proc parseAttrs(p: var Parser): ParsedAttrs =
|
|||||||
result.checked = true
|
result.checked = true
|
||||||
elif name == "Shared":
|
elif name == "Shared":
|
||||||
result.shared = true
|
result.shared = true
|
||||||
|
elif name == "Drop":
|
||||||
|
result.drop = true
|
||||||
|
elif name == "Release":
|
||||||
|
result.release = true
|
||||||
elif name == "Import":
|
elif name == "Import":
|
||||||
discard p.expect(tkLParen, "expected '('")
|
discard p.expect(tkLParen, "expected '('")
|
||||||
let key = p.expect(tkIdent, "expected attribute key").text
|
let key = p.expect(tkIdent, "expected attribute key").text
|
||||||
@@ -375,8 +390,8 @@ proc parsePattern(p: var Parser): Pattern =
|
|||||||
let inclusive = p.check(tkDotDotEqual)
|
let inclusive = p.check(tkDotDotEqual)
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
let right = p.parsePrimaryPattern()
|
let right = p.parsePrimaryPattern()
|
||||||
return Pattern(kind: pkRange, loc: loc, patRangeLo: left, patRangeHi: right, patRangeInclusive: inclusive)
|
left = Pattern(kind: pkRange, loc: loc, patRangeLo: left, patRangeHi: right, patRangeInclusive: inclusive)
|
||||||
# Guarded pattern
|
# Guarded pattern: `p if cond` (also after range: `1..10 if x % 2 == 0`)
|
||||||
if p.check(tkIf):
|
if p.check(tkIf):
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
let guard = p.parseExpr()
|
let guard = p.parseExpr()
|
||||||
@@ -1242,6 +1257,8 @@ proc parseFuncDecl(p: var Parser, isPublic: bool, isAsm: bool, attrs: ParsedAttr
|
|||||||
discard p.advance()
|
discard p.advance()
|
||||||
var declAttrs: seq[string] = @[]
|
var declAttrs: seq[string] = @[]
|
||||||
if attrs.checked: declAttrs.add("Checked")
|
if attrs.checked: declAttrs.add("Checked")
|
||||||
|
if attrs.release: declAttrs.add("Release")
|
||||||
|
if attrs.drop: declAttrs.add("Drop")
|
||||||
return Decl(kind: dkFunc, loc: loc, isPublic: isPublic,
|
return Decl(kind: dkFunc, loc: loc, isPublic: isPublic,
|
||||||
declAttrs: declAttrs,
|
declAttrs: declAttrs,
|
||||||
declFuncAsm: isAsm, declFuncCallConv: attrs.callConv,
|
declFuncAsm: isAsm, declFuncCallConv: attrs.callConv,
|
||||||
@@ -1250,7 +1267,7 @@ proc parseFuncDecl(p: var Parser, isPublic: bool, isAsm: bool, attrs: ParsedAttr
|
|||||||
declFuncParams: params, declFuncReturnType: retType,
|
declFuncParams: params, declFuncReturnType: retType,
|
||||||
declFuncBody: body)
|
declFuncBody: body)
|
||||||
|
|
||||||
proc parseStructDecl(p: var Parser, isPublic: bool): Decl =
|
proc parseStructDecl(p: var Parser, isPublic: bool, attrs: ParsedAttrs = ParsedAttrs()): Decl =
|
||||||
let loc = p.currentLoc
|
let loc = p.currentLoc
|
||||||
discard p.expect(tkStruct, "expected 'struct'")
|
discard p.expect(tkStruct, "expected 'struct'")
|
||||||
let name = p.expect(tkIdent, "expected struct name").text
|
let name = p.expect(tkIdent, "expected struct name").text
|
||||||
@@ -1279,7 +1296,10 @@ proc parseStructDecl(p: var Parser, isPublic: bool): Decl =
|
|||||||
if p.pos == startPos:
|
if p.pos == startPos:
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
discard p.expect(tkRBrace, "expected '}' to close struct")
|
discard p.expect(tkRBrace, "expected '}' to close struct")
|
||||||
return Decl(kind: dkStruct, loc: loc, isPublic: isPublic,
|
var declAttrs: seq[string] = @[]
|
||||||
|
if attrs.drop: declAttrs.add("Drop")
|
||||||
|
if attrs.checked: declAttrs.add("Checked")
|
||||||
|
return Decl(kind: dkStruct, loc: loc, isPublic: isPublic, declAttrs: declAttrs,
|
||||||
declStructName: name, declStructTypeParams: typeParams,
|
declStructName: name, declStructTypeParams: typeParams,
|
||||||
declStructFields: fields)
|
declStructFields: fields)
|
||||||
|
|
||||||
@@ -1567,7 +1587,7 @@ proc parseDecl(p: var Parser): Decl =
|
|||||||
of tkFunc:
|
of tkFunc:
|
||||||
return p.parseFuncDecl(isPublic, false, attrs, isConst, isAsync)
|
return p.parseFuncDecl(isPublic, false, attrs, isConst, isAsync)
|
||||||
of tkStruct:
|
of tkStruct:
|
||||||
return p.parseStructDecl(isPublic)
|
return p.parseStructDecl(isPublic, attrs)
|
||||||
of tkEnum:
|
of tkEnum:
|
||||||
return p.parseEnumDecl(isPublic)
|
return p.parseEnumDecl(isPublic)
|
||||||
of tkUnion:
|
of tkUnion:
|
||||||
|
|||||||
+305
-46
@@ -1,4 +1,4 @@
|
|||||||
import std/[strformat, tables, strutils]
|
import std/[strformat, tables, strutils, sets]
|
||||||
import ast, types, scope, source_location, token
|
import ast, types, scope, source_location, token
|
||||||
|
|
||||||
type
|
type
|
||||||
@@ -44,6 +44,12 @@ type
|
|||||||
checkedFunc*: bool ## true inside @[Checked] function
|
checkedFunc*: bool ## true inside @[Checked] function
|
||||||
currentFuncIsAsync*: bool ## true inside async func
|
currentFuncIsAsync*: bool ## true inside async func
|
||||||
movedVars*: seq[string] ## variables moved in current checked function
|
movedVars*: seq[string] ## variables moved in current checked function
|
||||||
|
## Active exclusive borrows: source var → borrow site (let-bound &mut lasts for rest of fn)
|
||||||
|
activeMutBorrows*: Table[string, SourceLocation]
|
||||||
|
## Active shared borrows of a source var (count of live &T lets)
|
||||||
|
activeSharedBorrows*: Table[string, int]
|
||||||
|
## When true, ekIdent skips use-while-borrowed (we're forming `&x` itself)
|
||||||
|
suppressUseWhileBorrow*: bool
|
||||||
currentRetType*: Type ## return type of the function being checked
|
currentRetType*: Type ## return type of the function being checked
|
||||||
closureDepth*: int ## nesting depth inside closures
|
closureDepth*: int ## nesting depth inside closures
|
||||||
currentClosureExpr*: Expr ## current closure being analyzed
|
currentClosureExpr*: Expr ## current closure being analyzed
|
||||||
@@ -101,6 +107,63 @@ proc hasErrors*(res: SemaResult): bool =
|
|||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Borrow checker helpers (@[Checked] exclusive &mut / shared & data-flow)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
proc extractBorrowedIdent*(e: Expr): string =
|
||||||
|
## Identify the source variable of `&x`, `borrow x`, `borrow &mut x`, etc.
|
||||||
|
if e == nil: return ""
|
||||||
|
case e.kind
|
||||||
|
of ekUnary:
|
||||||
|
if e.exprUnaryOp == tkAmp and e.exprUnaryOperand != nil:
|
||||||
|
if e.exprUnaryOperand.kind == ekIdent:
|
||||||
|
return e.exprUnaryOperand.exprIdent
|
||||||
|
if e.exprUnaryOperand.kind == ekUnary and e.exprUnaryOperand.exprUnaryOp == tkAmp and
|
||||||
|
e.exprUnaryOperand.exprUnaryOperand != nil and
|
||||||
|
e.exprUnaryOperand.exprUnaryOperand.kind == ekIdent:
|
||||||
|
return e.exprUnaryOperand.exprUnaryOperand.exprIdent
|
||||||
|
of ekBorrow:
|
||||||
|
return extractBorrowedIdent(e.exprBorrowOperand)
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
return ""
|
||||||
|
|
||||||
|
proc checkCreateBorrow(sema: var Sema, varName: string, isMut: bool, loc: SourceLocation) =
|
||||||
|
## Register a long-lived (let-bound) borrow of `varName` in a @[Checked] function.
|
||||||
|
if not sema.checkedFunc or varName.len == 0:
|
||||||
|
return
|
||||||
|
if isMut:
|
||||||
|
if sema.activeMutBorrows.hasKey(varName):
|
||||||
|
sema.emitError(loc, &"cannot mutably borrow '{varName}': already mutably borrowed")
|
||||||
|
return
|
||||||
|
if sema.activeSharedBorrows.getOrDefault(varName, 0) > 0:
|
||||||
|
sema.emitError(loc, &"cannot mutably borrow '{varName}' while it is shared-borrowed")
|
||||||
|
return
|
||||||
|
sema.activeMutBorrows[varName] = loc
|
||||||
|
else:
|
||||||
|
if sema.activeMutBorrows.hasKey(varName):
|
||||||
|
sema.emitError(loc, &"cannot shared-borrow '{varName}' while it is mutably borrowed")
|
||||||
|
return
|
||||||
|
sema.activeSharedBorrows[varName] = sema.activeSharedBorrows.getOrDefault(varName, 0) + 1
|
||||||
|
|
||||||
|
proc checkUseWhileBorrowed(sema: var Sema, varName: string, loc: SourceLocation, isWrite: bool) =
|
||||||
|
## Reject uses of a variable that has an active exclusive (&mut) borrow.
|
||||||
|
if not sema.checkedFunc or varName.len == 0 or sema.suppressUseWhileBorrow:
|
||||||
|
return
|
||||||
|
if sema.activeMutBorrows.hasKey(varName):
|
||||||
|
let kind = if isWrite: "assign to" else: "use"
|
||||||
|
sema.emitError(loc, &"cannot {kind} '{varName}' while it is mutably borrowed")
|
||||||
|
|
||||||
|
proc checkTempMutBorrow(sema: var Sema, varName: string, loc: SourceLocation) =
|
||||||
|
## Temporary &mut in a call argument — conflict with existing long-lived borrows.
|
||||||
|
if not sema.checkedFunc or varName.len == 0:
|
||||||
|
return
|
||||||
|
if sema.activeMutBorrows.hasKey(varName):
|
||||||
|
sema.emitError(loc, &"cannot mutably borrow '{varName}': already mutably borrowed")
|
||||||
|
elif sema.activeSharedBorrows.getOrDefault(varName, 0) > 0:
|
||||||
|
sema.emitError(loc, &"cannot mutably borrow '{varName}' while it is shared-borrowed")
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Generic type inference helpers
|
# Generic type inference helpers
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -172,6 +235,14 @@ proc typeToTypeExpr*(t: Type): TypeExpr =
|
|||||||
else:
|
else:
|
||||||
TypeExpr(kind: tekNamed, typeName: "void")
|
TypeExpr(kind: tekNamed, typeName: "void")
|
||||||
of tkVoid: TypeExpr(kind: tekNamed, typeName: "void")
|
of tkVoid: TypeExpr(kind: tekNamed, typeName: "void")
|
||||||
|
of tkFunc:
|
||||||
|
var params: seq[TypeExpr] = @[]
|
||||||
|
if t.inner.len > 0:
|
||||||
|
for i in 0 ..< t.inner.len - 1:
|
||||||
|
params.add(typeToTypeExpr(t.inner[i]))
|
||||||
|
let ret = typeToTypeExpr(t.inner[^1])
|
||||||
|
return TypeExpr(kind: tekFunc, funcParams: params, funcRet: ret)
|
||||||
|
TypeExpr(kind: tekNamed, typeName: "void")
|
||||||
else: TypeExpr(kind: tekNamed, typeName: t.toString)
|
else: TypeExpr(kind: tekNamed, typeName: t.toString)
|
||||||
|
|
||||||
proc substituteTypeInType(sema: var Sema, t: Type, subst: Table[string, Type]): Type =
|
proc substituteTypeInType(sema: var Sema, t: Type, subst: Table[string, Type]): Type =
|
||||||
@@ -213,58 +284,189 @@ proc substituteTypeInType(sema: var Sema, t: Type, subst: Table[string, Type]):
|
|||||||
else:
|
else:
|
||||||
return t
|
return t
|
||||||
|
|
||||||
|
proc unifyTypeParam(sema: var Sema, pattern: TypeExpr, concrete: Type,
|
||||||
|
tpNames: HashSet[string],
|
||||||
|
bindings: var Table[string, Type],
|
||||||
|
loc: SourceLocation): bool =
|
||||||
|
## Structural match of a parameter TypeExpr against a concrete argument Type.
|
||||||
|
## Binds type parameters (names in `tpNames`) into `bindings`.
|
||||||
|
## Returns false on hard mismatch; true when matching succeeds (unknowns ok).
|
||||||
|
if pattern == nil:
|
||||||
|
return true
|
||||||
|
if concrete == nil or concrete.isUnknown:
|
||||||
|
return true
|
||||||
|
|
||||||
|
case pattern.kind
|
||||||
|
of tekNamed:
|
||||||
|
let name = pattern.typeName
|
||||||
|
# Bare type parameter: T
|
||||||
|
if name in tpNames and pattern.typeArgs.len == 0:
|
||||||
|
if bindings.hasKey(name):
|
||||||
|
let prev = bindings[name]
|
||||||
|
if prev == concrete:
|
||||||
|
return true
|
||||||
|
if concrete.isAssignableTo(prev):
|
||||||
|
return true
|
||||||
|
if prev.isAssignableTo(concrete):
|
||||||
|
bindings[name] = concrete
|
||||||
|
return true
|
||||||
|
sema.emitError(loc,
|
||||||
|
&"conflicting types for type parameter '{name}': " &
|
||||||
|
&"{prev.toString} vs {concrete.toString}")
|
||||||
|
return false
|
||||||
|
bindings[name] = concrete
|
||||||
|
return true
|
||||||
|
# Named type with type args: Iter<T>, Array<U>, Map<K,V>
|
||||||
|
if pattern.typeArgs.len > 0:
|
||||||
|
if concrete.kind != tkNamed:
|
||||||
|
return false
|
||||||
|
# Allow monomorphized names like Iter_int? Prefer structural Iter + inner.
|
||||||
|
if concrete.name != name and not concrete.name.startsWith(name & "_"):
|
||||||
|
return false
|
||||||
|
if concrete.name == name:
|
||||||
|
if concrete.inner.len < pattern.typeArgs.len:
|
||||||
|
return false
|
||||||
|
for i, ta in pattern.typeArgs:
|
||||||
|
if i >= concrete.inner.len: break
|
||||||
|
if not sema.unifyTypeParam(ta, concrete.inner[i], tpNames, bindings, loc):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
# Monomorphized form Iter_int — best-effort: only if single type arg
|
||||||
|
if pattern.typeArgs.len == 1 and concrete.name.startsWith(name & "_"):
|
||||||
|
let suffix = concrete.name[name.len + 1 .. ^1]
|
||||||
|
# Only bind if pattern arg is a type param
|
||||||
|
let ta = pattern.typeArgs[0]
|
||||||
|
if ta != nil and ta.kind == tekNamed and ta.typeName in tpNames and ta.typeArgs.len == 0:
|
||||||
|
let mono = makeNamed(suffix)
|
||||||
|
# Prefer known primitives
|
||||||
|
let prim =
|
||||||
|
case suffix
|
||||||
|
of "int": makeInt()
|
||||||
|
of "bool": makeBool()
|
||||||
|
of "String", "str": makeStr()
|
||||||
|
of "float", "float64": makeFloat64()
|
||||||
|
else: mono
|
||||||
|
return sema.unifyTypeParam(ta, prim, tpNames, bindings, loc)
|
||||||
|
return false
|
||||||
|
# Concrete named type (not a param): int, String, Foo / primitives
|
||||||
|
let expected =
|
||||||
|
case name
|
||||||
|
of "int": makeInt()
|
||||||
|
of "int8": makeInt8()
|
||||||
|
of "int16": makeInt16()
|
||||||
|
of "int32": makeInt32()
|
||||||
|
of "int64": makeInt64()
|
||||||
|
of "uint": makeUInt()
|
||||||
|
of "uint8": makeUInt8()
|
||||||
|
of "uint16": makeUInt16()
|
||||||
|
of "uint32": makeUInt32()
|
||||||
|
of "uint64": makeUInt64()
|
||||||
|
of "bool": makeBool()
|
||||||
|
of "float", "float64": makeFloat64()
|
||||||
|
of "float32": makeFloat32()
|
||||||
|
of "String", "str": makeStr()
|
||||||
|
of "void": makeVoid()
|
||||||
|
else: makeNamed(name)
|
||||||
|
if concrete == expected:
|
||||||
|
return true
|
||||||
|
if concrete.kind == tkNamed and expected.kind == tkNamed:
|
||||||
|
return concrete.name == expected.name
|
||||||
|
return concrete.isAssignableTo(expected) or expected.isAssignableTo(concrete)
|
||||||
|
|
||||||
|
of tekPointer, tekOwn:
|
||||||
|
if not concrete.isPointer or concrete.inner.len == 0:
|
||||||
|
return false
|
||||||
|
return sema.unifyTypeParam(pattern.pointerPointee, concrete.inner[0], tpNames, bindings, loc)
|
||||||
|
|
||||||
|
of tekRef, tekMutRef:
|
||||||
|
if not concrete.isPointer or concrete.inner.len == 0:
|
||||||
|
return false
|
||||||
|
return sema.unifyTypeParam(pattern.pointerPointee, concrete.inner[0], tpNames, bindings, loc)
|
||||||
|
|
||||||
|
of tekFunc:
|
||||||
|
# concrete: tkFunc with inner = params ++ [ret]
|
||||||
|
if concrete.kind != tkFunc:
|
||||||
|
return false
|
||||||
|
let nParams = pattern.funcParams.len
|
||||||
|
if concrete.inner.len != nParams + 1:
|
||||||
|
# Allow fat-func mismatch length if unknown
|
||||||
|
return false
|
||||||
|
for i, p in pattern.funcParams:
|
||||||
|
if not sema.unifyTypeParam(p, concrete.inner[i], tpNames, bindings, loc):
|
||||||
|
return false
|
||||||
|
if pattern.funcRet != nil:
|
||||||
|
if not sema.unifyTypeParam(pattern.funcRet, concrete.inner[^1], tpNames, bindings, loc):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
of tekSlice:
|
||||||
|
if not concrete.isSlice or concrete.inner.len == 0:
|
||||||
|
return false
|
||||||
|
return sema.unifyTypeParam(pattern.sliceElement, concrete.inner[0], tpNames, bindings, loc)
|
||||||
|
|
||||||
|
of tekTuple:
|
||||||
|
if concrete.kind != tkTuple or concrete.inner.len != pattern.tupleElements.len:
|
||||||
|
return false
|
||||||
|
for i, elem in pattern.tupleElements:
|
||||||
|
if not sema.unifyTypeParam(elem, concrete.inner[i], tpNames, bindings, loc):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
of tekPath, tekDynRef, tekSelf:
|
||||||
|
return true # best-effort skip
|
||||||
|
|
||||||
proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type],
|
proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type],
|
||||||
loc: SourceLocation): seq[TypeExpr] =
|
loc: SourceLocation): seq[TypeExpr] =
|
||||||
## Infer type arguments from argument types for a generic function call.
|
## Infer type arguments from argument types for a generic function call.
|
||||||
|
## Uses structural matching so `*Iter<T>` + `func(T)->U` yield T and U.
|
||||||
## Returns empty seq if inference fails for any type parameter.
|
## Returns empty seq if inference fails for any type parameter.
|
||||||
result = @[]
|
result = @[]
|
||||||
|
var tpNames = initHashSet[string]()
|
||||||
|
for tp in funcDecl.declFuncTypeParams:
|
||||||
|
if not tp.isLifetime:
|
||||||
|
tpNames.incl(tp.name)
|
||||||
|
|
||||||
|
var bindings = initTable[string, Type]()
|
||||||
|
|
||||||
|
# Lifetime params: mark as found if any ref param uses them
|
||||||
for tp in funcDecl.declFuncTypeParams:
|
for tp in funcDecl.declFuncTypeParams:
|
||||||
let tpName = tp.name
|
|
||||||
# Lifetime params are inferred from ref lifetime positions
|
|
||||||
if tp.isLifetime:
|
if tp.isLifetime:
|
||||||
var found = false
|
var found = false
|
||||||
for i, param in funcDecl.declFuncParams:
|
for i, param in funcDecl.declFuncParams:
|
||||||
if i >= argTypes.len: break
|
if i >= argTypes.len: break
|
||||||
if param.ptype.kind in {tekRef, tekMutRef} and param.ptype.refLifetime == tpName:
|
if param.ptype.kind in {tekRef, tekMutRef} and param.ptype.refLifetime == tp.name:
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
if found:
|
if not found:
|
||||||
result.add(TypeExpr(kind: tekNamed, typeName: "lifetime"))
|
|
||||||
continue
|
|
||||||
# If not found in refs, treat as uninferrable
|
|
||||||
return @[]
|
return @[]
|
||||||
var inferred: Type = nil
|
# lifetimes don't go into monomorph name; placeholder
|
||||||
|
bindings[tp.name] = makeNamed("lifetime")
|
||||||
|
|
||||||
|
# Unify each param pattern with the corresponding argument type
|
||||||
for i, param in funcDecl.declFuncParams:
|
for i, param in funcDecl.declFuncParams:
|
||||||
if i >= argTypes.len: break
|
if i >= argTypes.len: break
|
||||||
# Skip pointer params — type param is inside the pointee and we cannot
|
if param.ptype == nil: continue
|
||||||
# structurally extract it (e.g., *Map<K,V> → arg is *Map<int,String>)
|
var refsTp = false
|
||||||
if param.ptype.kind in {tekOwn, tekPointer}:
|
for n in tpNames:
|
||||||
|
if typeExprReferencesTypeParam(param.ptype, n):
|
||||||
|
refsTp = true
|
||||||
|
break
|
||||||
|
if not refsTp:
|
||||||
continue
|
continue
|
||||||
if typeExprReferencesTypeParam(param.ptype, tpName):
|
if not sema.unifyTypeParam(param.ptype, argTypes[i], tpNames, bindings, loc):
|
||||||
var argType = argTypes[i]
|
|
||||||
# If type param is inside a ref/pointer pointee, unwrap the arg type
|
|
||||||
if param.ptype.kind in {tekRef, tekMutRef, tekPointer} and
|
|
||||||
typeExprReferencesTypeParam(param.ptype.pointerPointee, tpName) and
|
|
||||||
argType.isPointer and argType.inner.len > 0:
|
|
||||||
argType = argType.inner[0]
|
|
||||||
if inferred == nil:
|
|
||||||
inferred = argType
|
|
||||||
elif inferred != argType:
|
|
||||||
# Check if one is assignable to the other (wider type wins)
|
|
||||||
if argTypes[i].isAssignableTo(inferred):
|
|
||||||
discard # inferred stays the same
|
|
||||||
elif inferred.isAssignableTo(argTypes[i]):
|
|
||||||
inferred = argTypes[i]
|
|
||||||
else:
|
|
||||||
sema.emitError(loc,
|
|
||||||
&"conflicting types for type parameter '{tpName}': " &
|
|
||||||
&"{inferred.toString} vs {argType.toString}")
|
|
||||||
return @[]
|
return @[]
|
||||||
if inferred != nil and not inferred.isUnknown:
|
|
||||||
result.add(typeToTypeExpr(inferred))
|
# Emit results in decl order
|
||||||
else:
|
for tp in funcDecl.declFuncTypeParams:
|
||||||
# Cannot infer this type parameter from arguments
|
if tp.isLifetime:
|
||||||
|
result.add(TypeExpr(kind: tekNamed, typeName: "lifetime"))
|
||||||
|
continue
|
||||||
|
if not bindings.hasKey(tp.name):
|
||||||
return @[]
|
return @[]
|
||||||
|
let t = bindings[tp.name]
|
||||||
|
if t == nil or t.isUnknown:
|
||||||
|
return @[]
|
||||||
|
result.add(typeToTypeExpr(t))
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Type resolution from AST TypeExpr
|
# Type resolution from AST TypeExpr
|
||||||
@@ -914,6 +1116,8 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
|||||||
if sema.checkedFunc and expr.exprIdent in sema.movedVars:
|
if sema.checkedFunc and expr.exprIdent in sema.movedVars:
|
||||||
sema.emitError(expr.loc, &"use of moved value '{expr.exprIdent}'")
|
sema.emitError(expr.loc, &"use of moved value '{expr.exprIdent}'")
|
||||||
return makeUnknown()
|
return makeUnknown()
|
||||||
|
# Exclusive borrow: cannot read original while &mut is live
|
||||||
|
sema.checkUseWhileBorrowed(expr.exprIdent, expr.loc, isWrite = false)
|
||||||
let sym = scope.lookup(expr.exprIdent)
|
let sym = scope.lookup(expr.exprIdent)
|
||||||
if sym == nil:
|
if sym == nil:
|
||||||
sema.emitError(expr.loc, &"undeclared identifier '{expr.exprIdent}'")
|
sema.emitError(expr.loc, &"undeclared identifier '{expr.exprIdent}'")
|
||||||
@@ -946,7 +1150,15 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
|||||||
return makeUnknown()
|
return makeUnknown()
|
||||||
return first.typ
|
return first.typ
|
||||||
of ekUnary:
|
of ekUnary:
|
||||||
let operandType = sema.checkExpr(expr.exprUnaryOperand, scope)
|
# Forming `&x` must not count as a "use" of x (borrow creation is checked separately)
|
||||||
|
var operandType: Type
|
||||||
|
if expr.exprUnaryOp == tkAmp:
|
||||||
|
let savedSup = sema.suppressUseWhileBorrow
|
||||||
|
sema.suppressUseWhileBorrow = true
|
||||||
|
operandType = sema.checkExpr(expr.exprUnaryOperand, scope)
|
||||||
|
sema.suppressUseWhileBorrow = savedSup
|
||||||
|
else:
|
||||||
|
operandType = sema.checkExpr(expr.exprUnaryOperand, scope)
|
||||||
case expr.exprUnaryOp
|
case expr.exprUnaryOp
|
||||||
of tkBang:
|
of tkBang:
|
||||||
if not operandType.isBool:
|
if not operandType.isBool:
|
||||||
@@ -1035,7 +1247,16 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
|||||||
let movedIdx = sema.movedVars.find(expr.exprAssignTarget.exprIdent)
|
let movedIdx = sema.movedVars.find(expr.exprAssignTarget.exprIdent)
|
||||||
if movedIdx >= 0:
|
if movedIdx >= 0:
|
||||||
sema.movedVars.delete(movedIdx)
|
sema.movedVars.delete(movedIdx)
|
||||||
let target = sema.checkExpr(expr.exprAssignTarget, scope)
|
# Cannot assign to var while it is mutably borrowed (single message; suppress rvalue use-check)
|
||||||
|
sema.checkUseWhileBorrowed(expr.exprAssignTarget.exprIdent, expr.loc, isWrite = true)
|
||||||
|
var target: Type
|
||||||
|
if expr.exprAssignTarget != nil and expr.exprAssignTarget.kind == ekIdent:
|
||||||
|
let savedSup = sema.suppressUseWhileBorrow
|
||||||
|
sema.suppressUseWhileBorrow = true
|
||||||
|
target = sema.checkExpr(expr.exprAssignTarget, scope)
|
||||||
|
sema.suppressUseWhileBorrow = savedSup
|
||||||
|
else:
|
||||||
|
target = sema.checkExpr(expr.exprAssignTarget, scope)
|
||||||
let value = sema.checkExpr(expr.exprAssignValue, scope)
|
let value = sema.checkExpr(expr.exprAssignValue, scope)
|
||||||
if not value.isAssignableTo(target):
|
if not value.isAssignableTo(target):
|
||||||
sema.emitError(expr.loc, &"cannot assign {value.toString} to {target.toString}")
|
sema.emitError(expr.loc, &"cannot assign {value.toString} to {target.toString}")
|
||||||
@@ -1207,8 +1428,16 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
|||||||
for i in 0 ..< argTypes.len:
|
for i in 0 ..< argTypes.len:
|
||||||
if expectedParams[i].isMutRef and i < expr.exprCallArgs.len:
|
if expectedParams[i].isMutRef and i < expr.exprCallArgs.len:
|
||||||
let arg = expr.exprCallArgs[i]
|
let arg = expr.exprCallArgs[i]
|
||||||
if arg.kind == ekUnary and arg.exprUnaryOp == tkAmp and arg.exprUnaryOperand.kind == ekIdent:
|
let bname = extractBorrowedIdent(arg)
|
||||||
mutRefArgs.add((idx: i, name: arg.exprUnaryOperand.exprIdent))
|
if bname.len > 0:
|
||||||
|
mutRefArgs.add((idx: i, name: bname))
|
||||||
|
# Conflict with long-lived let-bound &mut
|
||||||
|
sema.checkTempMutBorrow(bname, arg.loc)
|
||||||
|
elif expectedParams[i].isRef and i < expr.exprCallArgs.len:
|
||||||
|
let bname = extractBorrowedIdent(expr.exprCallArgs[i])
|
||||||
|
if bname.len > 0 and sema.activeMutBorrows.hasKey(bname):
|
||||||
|
sema.emitError(expr.exprCallArgs[i].loc,
|
||||||
|
&"cannot shared-borrow '{bname}' while it is mutably borrowed")
|
||||||
for i in 0 ..< mutRefArgs.len:
|
for i in 0 ..< mutRefArgs.len:
|
||||||
for j in i+1 ..< mutRefArgs.len:
|
for j in i+1 ..< mutRefArgs.len:
|
||||||
if mutRefArgs[i].name == mutRefArgs[j].name:
|
if mutRefArgs[i].name == mutRefArgs[j].name:
|
||||||
@@ -1472,6 +1701,11 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
|||||||
for arm in expr.exprMatchArms:
|
for arm in expr.exprMatchArms:
|
||||||
var armScope = newScope(scope)
|
var armScope = newScope(scope)
|
||||||
sema.extractPatternBindings(arm.pattern, armScope, subjectType)
|
sema.extractPatternBindings(arm.pattern, armScope, subjectType)
|
||||||
|
# Type-check `p if guard` condition (must be bool; sees pattern bindings)
|
||||||
|
if arm.pattern != nil and arm.pattern.kind == pkGuarded and arm.pattern.patGuardedExpr != nil:
|
||||||
|
let guardTy = sema.checkExpr(arm.pattern.patGuardedExpr, armScope)
|
||||||
|
if not guardTy.isBool and not guardTy.isUnknown:
|
||||||
|
sema.emitError(arm.pattern.patGuardedExpr.loc, "match guard condition must be bool")
|
||||||
let armType = sema.checkExpr(arm.body, armScope)
|
let armType = sema.checkExpr(arm.body, armScope)
|
||||||
if resultType.isUnknown:
|
if resultType.isUnknown:
|
||||||
resultType = armType
|
resultType = armType
|
||||||
@@ -1507,13 +1741,19 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
|||||||
return makePointer(makeVoid())
|
return makePointer(makeVoid())
|
||||||
of ekBorrow:
|
of ekBorrow:
|
||||||
let operand = sema.checkExpr(expr.exprBorrowOperand, scope)
|
let operand = sema.checkExpr(expr.exprBorrowOperand, scope)
|
||||||
# borrow &mut expr returns the same type as the original (reference)
|
# Explicit `borrow` — track only when bound via let (checkCreateBorrow on skLet).
|
||||||
# The borrow is tracked in the borrow checker
|
# Here we validate conflicts for free-standing borrow expressions used as temps.
|
||||||
if sema.checkedFunc and expr.exprBorrowMutable:
|
if sema.checkedFunc:
|
||||||
# Track: variable "operand" is mutably borrowed here
|
let bname = extractBorrowedIdent(expr)
|
||||||
# For now, just validate the type
|
if bname.len > 0:
|
||||||
discard
|
if expr.exprBorrowMutable:
|
||||||
return operand
|
sema.checkTempMutBorrow(bname, expr.loc)
|
||||||
|
elif sema.activeMutBorrows.hasKey(bname):
|
||||||
|
sema.emitError(expr.loc,
|
||||||
|
&"cannot shared-borrow '{bname}' while it is mutably borrowed")
|
||||||
|
if expr.exprBorrowMutable:
|
||||||
|
return makeMutRef(operand)
|
||||||
|
return makeRef(operand)
|
||||||
of ekSpread:
|
of ekSpread:
|
||||||
return sema.checkExpr(expr.exprSpreadOperand, scope)
|
return sema.checkExpr(expr.exprSpreadOperand, scope)
|
||||||
of ekStringInterp:
|
of ekStringInterp:
|
||||||
@@ -1582,6 +1822,19 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
|
|||||||
let initSym = scope.lookup(stmt.stmtLetInit.exprIdent)
|
let initSym = scope.lookup(stmt.stmtLetInit.exprIdent)
|
||||||
if initSym != nil and initSym.isOwn:
|
if initSym != nil and initSym.isOwn:
|
||||||
sema.movedVars.add(stmt.stmtLetInit.exprIdent)
|
sema.movedVars.add(stmt.stmtLetInit.exprIdent)
|
||||||
|
# Long-lived borrow: `let r: &mut T = &x` / `let r: &T = &x`
|
||||||
|
if sema.checkedFunc and stmt.stmtLetInit != nil:
|
||||||
|
let bname = extractBorrowedIdent(stmt.stmtLetInit)
|
||||||
|
if bname.len > 0:
|
||||||
|
var isMut = false
|
||||||
|
if declaredType.isMutRef:
|
||||||
|
isMut = true
|
||||||
|
elif declaredType.isRef:
|
||||||
|
isMut = false
|
||||||
|
else:
|
||||||
|
# Untyped let + `&x` is typed as &mut by unary lowering
|
||||||
|
isMut = initType.isMutRef
|
||||||
|
sema.checkCreateBorrow(bname, isMut, stmt.stmtLetInit.loc)
|
||||||
return makeVoid()
|
return makeVoid()
|
||||||
of skIf:
|
of skIf:
|
||||||
let condType = sema.checkExpr(stmt.stmtIfCond, scope)
|
let condType = sema.checkExpr(stmt.stmtIfCond, scope)
|
||||||
@@ -1631,6 +1884,10 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
|
|||||||
for arm in stmt.stmtMatchArms:
|
for arm in stmt.stmtMatchArms:
|
||||||
var armScope = newScope(scope)
|
var armScope = newScope(scope)
|
||||||
sema.extractPatternBindings(arm.pattern, armScope, subjectType)
|
sema.extractPatternBindings(arm.pattern, armScope, subjectType)
|
||||||
|
if arm.pattern != nil and arm.pattern.kind == pkGuarded and arm.pattern.patGuardedExpr != nil:
|
||||||
|
let guardTy = sema.checkExpr(arm.pattern.patGuardedExpr, armScope)
|
||||||
|
if not guardTy.isBool and not guardTy.isUnknown:
|
||||||
|
sema.emitError(arm.pattern.patGuardedExpr.loc, "match guard condition must be bool")
|
||||||
discard sema.checkExpr(arm.body, armScope)
|
discard sema.checkExpr(arm.body, armScope)
|
||||||
return makeVoid()
|
return makeVoid()
|
||||||
of skReturn:
|
of skReturn:
|
||||||
@@ -1704,6 +1961,8 @@ proc checkFunc(sema: var Sema, decl: Decl) =
|
|||||||
sema.currentFuncIsAsync = decl.declFuncIsAsync
|
sema.currentFuncIsAsync = decl.declFuncIsAsync
|
||||||
if sema.checkedFunc:
|
if sema.checkedFunc:
|
||||||
sema.movedVars = @[]
|
sema.movedVars = @[]
|
||||||
|
sema.activeMutBorrows = initTable[string, SourceLocation]()
|
||||||
|
sema.activeSharedBorrows = initTable[string, int]()
|
||||||
var funcScope = newScope(sema.globalScope)
|
var funcScope = newScope(sema.globalScope)
|
||||||
# Add type parameters to type table for resolution
|
# Add type parameters to type table for resolution
|
||||||
var addedTypeParams: seq[string] = @[]
|
var addedTypeParams: seq[string] = @[]
|
||||||
|
|||||||
+102
-12
@@ -1,7 +1,7 @@
|
|||||||
# Bux — План към „добър“ език (v0.5 → v1.0)
|
# Bux — План към „добър“ език (v0.5 → v1.0)
|
||||||
|
|
||||||
> **Дата:** 2026-07-18
|
> **Дата:** 2026-07-18
|
||||||
> **Текущо:** v0.5.x — selfhost loop, gradual ownership, green threads, **41+ examples**, match + pattern bindings + **`f"..."` interp** bootstrap+selfhost ✅
|
> **Текущо:** v0.5.x — selfhost loop, gradual ownership, green threads, **43+ examples**, match + guards + **generic HOF inference** + pattern bindings + **`f"..."` interp** bootstrap+selfhost ✅
|
||||||
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -59,6 +59,7 @@
|
|||||||
| B.2 | Function pointer types | `func(T)->U` fat ABI | ✅ bootstrap + selfhost |
|
| B.2 | Function pointer types | `func(T)->U` fat ABI | ✅ bootstrap + selfhost |
|
||||||
| B.3 | Match expression lowering (literals, ranges, enums) | Expression-context match → if-else | ✅ bootstrap + selfhost |
|
| B.3 | Match expression lowering (literals, ranges, enums) | Expression-context match → if-else | ✅ bootstrap + selfhost |
|
||||||
| B.3b | Pattern bindings (`Some(value) => value`) | Payload idents bound in arm body | ✅ bootstrap + selfhost |
|
| B.3b | Pattern bindings (`Some(value) => value`) | Payload idents bound in arm body | ✅ bootstrap + selfhost |
|
||||||
|
| B.3c | Match arm guards (`p if cond => …`) | Bindings visible in guard; sequential found-flag lower | ✅ bootstrap + selfhost |
|
||||||
| B.4 | Closures multi-instance | Fat `BuxFn` + heap env | ✅ bootstrap + selfhost |
|
| B.4 | Closures multi-instance | Fat `BuxFn` + heap env | ✅ bootstrap + selfhost |
|
||||||
| B.4b | Closures: `\|\|` empty params + loop/return body | Lexer `\|\|` vs empty closure; while/break/return | ✅ bootstrap + selfhost |
|
| B.4b | Closures: `\|\|` empty params + loop/return body | Lexer `\|\|` vs empty closure; while/break/return | ✅ bootstrap + selfhost |
|
||||||
| B.5 | По-добри diagnostics (snippet + hint) | DX #1 за нови потребители | ✅ |
|
| B.5 | По-добри diagnostics (snippet + hint) | DX #1 за нови потребители | ✅ |
|
||||||
@@ -66,18 +67,18 @@
|
|||||||
|
|
||||||
### C — Gradual Ownership 2.0 (P1)
|
### C — Gradual Ownership 2.0 (P1)
|
||||||
|
|
||||||
| # | Задача | Защо |
|
| # | Задача | Защо | Статус |
|
||||||
|---|--------|------|
|
|---|--------|------|--------|
|
||||||
| C.1 | Lifetime elision за common cases | Без `'a` в 90% от API-тата |
|
| C.1 | Lifetime elision за common cases | Без `'a` в 90% от API-тата | ⏳ |
|
||||||
| C.2 | Exclusive `&mut` vs shared `&` data-flow | По-малко false negatives |
|
| C.2 | Exclusive `&mut` vs shared `&` data-flow | По-малко false negatives | ✅ let-bound + use-while + call conflict |
|
||||||
| C.3 | Auto-drop edge cases (early return, branches) | RAII да е надежден |
|
| C.3 | Auto-drop edge cases (early return, branches) | RAII да е надежден | ✅ bootstrap + selfhost |
|
||||||
| C.4 | `@[Release]` zero-cost path документация + golden tests | Killer story: safe default, free hot path |
|
| C.4 | `@[Release]` zero-cost path документация + golden tests | Killer story: safe default, free hot path | ✅ partial (unchecked path + goldens) |
|
||||||
|
|
||||||
### D — Tooling (P1)
|
### D — Tooling (P1)
|
||||||
|
|
||||||
| # | Задача | Защо | Статус |
|
| # | Задача | Защо | Статус |
|
||||||
|---|--------|------|--------|
|
|---|--------|------|--------|
|
||||||
| D.1 | LSP: hover, go-to-def, diagnostics | IDE = adoption | ✅ hover/def/outline + `buxc` diags (lightweight index; full sema later) |
|
| D.1 | LSP: hover, go-to-def, diagnostics | IDE = adoption | ✅ hover/def/outline + **sema types on hover** (v0.3.0) + `buxc` diags |
|
||||||
| D.2 | `bux fmt` стабилен + CI check | Единен style | ⏳ |
|
| D.2 | `bux fmt` стабилен + CI check | Единен style | ⏳ |
|
||||||
| D.3 | `bux test` с `--filter`, exit codes, summary table | CI-friendly | ⏳ partial (`bux test` exists) |
|
| D.3 | `bux test` с `--filter`, exit codes, summary table | CI-friendly | ⏳ partial (`bux test` exists) |
|
||||||
| D.4 | `bux doc` от `///` comments | Самодокументиращ се stdlib | ⏳ |
|
| D.4 | `bux doc` от `///` comments | Самодокументиращ се stdlib | ⏳ |
|
||||||
@@ -297,9 +298,98 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Сесия 18 (match arm guards — B.3c)
|
||||||
|
|
||||||
|
1. **Syntax:** `p if cond => body` (also after ranges: `1..10 if x % 2 == 0`)
|
||||||
|
2. **Parser fix (critical):** `isTypeArgListAhead` treated `x < 0` as generic when a later `x > 0` existed in another arm → infinite parse. Stop lookahead on `=>`, keywords, literals, arithmetic, comparisons.
|
||||||
|
3. **Sema:** bind inner pattern first; type-check guard as bool in arm scope
|
||||||
|
4. **HIR lower:** sequential `found` flag (no shared if-else DAG):
|
||||||
|
```
|
||||||
|
if (!found) { if (inner_cond) { binds; if (guard) { result = body; found = true; } } }
|
||||||
|
```
|
||||||
|
Bindings are in scope for the guard expression.
|
||||||
|
5. **Selfhost:** `pkGuarded` + `patGuardExpr`; same lower strategy; fix `return match {…}` to expand yield block before return
|
||||||
|
6. Example: `examples/match_guards.bux` (ident/literal/range/enum payload guards)
|
||||||
|
7. Verified: bootstrap + **buxc2** + all examples + error goldens + **selfhost-loop IDENTICAL ✓**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Сесия 19 (generic HOF type inference)
|
||||||
|
|
||||||
|
1. **Bootstrap `inferTypeArgs`:** structural unify of param TypeExpr vs arg Type
|
||||||
|
- `*Iter<T>` / `*Array<T>` → extract T from pointee type args or mangled `Array_int`
|
||||||
|
- `func(T)->U` → bind T/U from function-value type (not whole func as T)
|
||||||
|
- bare `Acc` from init; multi-param `Iter_Fold<T,Acc>`
|
||||||
|
2. **Selfhost:** improved `Sema_InferGenericArgs` + return-type subst after inference
|
||||||
|
- Fix: `ekCast` must type-check operand (was skipping → no inference under `as`)
|
||||||
|
- HIR fallback mono from first *Array/*Iter arg when count=0
|
||||||
|
3. Works without explicit type args:
|
||||||
|
- `Array_Push(&nums, 1)`, `Array_Get`, `Array_Len`, `Array_Iter`
|
||||||
|
- `Iter_Map(&it, f)`, `Iter_Filter`, `Iter_Fold`, `Iter_Any` (int↔String)
|
||||||
|
4. Example: `examples/generic_infer_hof.bux`
|
||||||
|
5. Verified: bootstrap + **buxc2** + all examples + **selfhost-loop IDENTICAL ✓**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Сесия 20 (LSP hover from real sema — D.1)
|
||||||
|
|
||||||
|
1. **`bux-lsp` 0.3.0** links bootstrap (`--path:../bootstrap`) and runs `analyzeFull` on open/save
|
||||||
|
2. **typeIndex:** global scope (stdlib + file) → hover signatures with real types
|
||||||
|
3. **File-local priority:** user decls override stdlib name collisions (`Max<T>` vs `Math.Max`)
|
||||||
|
4. **Locals:** walk function bodies for `let`/`var` with explicit type annotations
|
||||||
|
5. **didChange:** fast lightweight rescan; keeps previous typeIndex until save/hover refresh
|
||||||
|
6. Hover shows ```bux signature``` + `_kind_ · sema`
|
||||||
|
7. Smoke: `Main() -> int`, `PrintLine(String) -> void`, `Max<T>(a: T, b: T) -> T`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Сесия 21 (pattern binding shadowing)
|
||||||
|
|
||||||
|
1. **Problem:** C/LIR function-scoped locals — nested `Some(n) => match … Some(n)` emitted store before `int n`, and `let v` + pattern `v` caused redeclaration.
|
||||||
|
2. **Bootstrap:** every pattern binding → unique C name `__pN_src` via `patternRenames` map; body/guard idents rewritten; **binds before body lower** inside `lowerMatch`.
|
||||||
|
3. **Selfhost:** same model — `Lcx_BindPatIdent` + `patMapFrom/To` rename table; arm-scoped push/pop of map.
|
||||||
|
4. Semantics: nested pattern name shadows correctly; outer `let v` survives after match that binds `v`.
|
||||||
|
5. Example: `examples/pattern_shadow.bux`
|
||||||
|
6. Verified: bootstrap + **buxc2** + all examples + **selfhost-loop IDENTICAL ✓**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Сесия 22 (Ownership 2.0 — C.2 + C.4 + *p= fix)
|
||||||
|
|
||||||
|
1. **C.2 Exclusive &mut data-flow** (`@[Checked]`):
|
||||||
|
- Track long-lived let-bound borrows (`activeMutBorrows` / `activeSharedBorrows`)
|
||||||
|
- Reject: second `&mut x`, use/assign of `x` while mutably borrowed, `&x` while `&mut` live
|
||||||
|
- Call-site temps conflict with existing let-bound borrows
|
||||||
|
2. **C.4 Golden tests:**
|
||||||
|
- `tests/error_golden/exclusive_mut_let/`
|
||||||
|
- `tests/error_golden/use_while_mut_borrow/`
|
||||||
|
3. **Bugfix:** `*p = expr` now stores through the pointer (was assigning to a temp) — ownership examples finally mutate correctly
|
||||||
|
4. Example: `examples/ownership_checked.bux` (unchecked zero-cost + checked OK path)
|
||||||
|
5. Verified: 7 error goldens + borrow_test + all examples + selfhost-loop
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Сесия 23 (Ownership 2.0 — C.3 auto-drop early return / branches)
|
||||||
|
|
||||||
|
1. **Bootstrap auto-drop for `@[Drop]` + collections:**
|
||||||
|
- Parser: `@[Drop]` / `@[Release]` on structs and funcs (`declAttrs`)
|
||||||
|
- `autoDropFuncName` + monomorphize `Array_Drop`/`Free` (etc.) so stdlib links
|
||||||
|
- Inject `Type_Drop(&x)` on `let` via `deferStmts`
|
||||||
|
2. **Early return / multi-path:**
|
||||||
|
- Every `return` snapshots the full live defer stack (no clear-after-first-return)
|
||||||
|
- Materialize return value **before** Drop (`return a.id` is not use-after-drop)
|
||||||
|
- Move-on-return: skip Drop for a local returned by value (`return out`)
|
||||||
|
3. **Branch / loop scopes:**
|
||||||
|
- Bootstrap: `lowerBlock` scopes `deferStmts` — branch-local drops at block exit; siblings do not see each other
|
||||||
|
- Selfhost C backend: `CBE_EmitDefers` keeps stack for multi-return; `CBE_EmitAndPopDefersFrom` pops branch/loop locals after `if`/`while`/`loop`
|
||||||
|
4. **Selfhost fixes:** null-safe ret type; temp name counter; use function `retTypeName` for `__retdrop_N`
|
||||||
|
5. Example: `examples/drop_early_return.bux` (Early + Branched + Scoped → 5 drops)
|
||||||
|
6. Verified: bootstrap + **buxc2** drop tests, 7 error goldens, key examples, **selfhost-loop IDENTICAL ✓**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Следващи стъпки
|
## Следващи стъпки
|
||||||
|
|
||||||
1. LSP: wire hover types from real sema
|
1. C.1 Lifetime elision
|
||||||
2. Generic type inference for `Iter_Map` without explicit `<T,U>`
|
2. Phase D tooling: `bux fmt` CI, `bux test --filter`, golden stdlib tests
|
||||||
3. Match arm guards (`p if cond => …`)
|
3. LSP: position-sensitive locals; inferred `let` types
|
||||||
4. Pattern binding name shadowing (C locals are function-scoped)
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// C.3 — Auto-drop on early return and if branches
|
||||||
|
// @[Drop] types call Type_Drop at every exit (return + branch scope end).
|
||||||
|
|
||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Test::{Test_AssertEqInt, Test_Pass};
|
||||||
|
|
||||||
|
@[Drop]
|
||||||
|
struct Token {
|
||||||
|
id: int,
|
||||||
|
counter: *int
|
||||||
|
}
|
||||||
|
|
||||||
|
func Token_Drop(self: *Token) {
|
||||||
|
if self.counter != null as *int {
|
||||||
|
*self.counter = *self.counter + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop t on both early return and fallthrough return.
|
||||||
|
func Early(flag: int, counter: *int) -> int {
|
||||||
|
let t: Token = Token { id: 1, counter: counter };
|
||||||
|
if flag == 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Branch-local Tokens: only the taken branch's Drop runs.
|
||||||
|
func Branched(flag: int, counter: *int) -> int {
|
||||||
|
if flag == 1 {
|
||||||
|
let a: Token = Token { id: 10, counter: counter };
|
||||||
|
return a.id;
|
||||||
|
} else {
|
||||||
|
let b: Token = Token { id: 20, counter: counter };
|
||||||
|
return b.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallthrough: Drop at end of block without return.
|
||||||
|
func Scoped(counter: *int) {
|
||||||
|
let s: Token = Token { id: 99, counter: counter };
|
||||||
|
discard s.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
var drops: int = 0;
|
||||||
|
discard Early(0, &drops);
|
||||||
|
discard Early(1, &drops);
|
||||||
|
Test_AssertEqInt(drops, 2);
|
||||||
|
|
||||||
|
discard Branched(1, &drops);
|
||||||
|
discard Branched(0, &drops);
|
||||||
|
Test_AssertEqInt(drops, 4);
|
||||||
|
|
||||||
|
Scoped(&drops);
|
||||||
|
Test_AssertEqInt(drops, 5);
|
||||||
|
|
||||||
|
PrintInt(drops);
|
||||||
|
PrintLine("");
|
||||||
|
Test_Pass("drop_early_return");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
// Generic HOF type inference — Iter_Map / Filter / Fold without explicit <T,U>
|
||||||
|
// Also Array_Push / Array_Get / Array_Iter inferred from arguments.
|
||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Array::{
|
||||||
|
Array, Array_New, Array_Push, Array_Get, Array_Len, Array_Free
|
||||||
|
};
|
||||||
|
import Std::String::{String_FromInt, String_Len, String_Eq};
|
||||||
|
import Std::Iter::{
|
||||||
|
Array_Iter, Iter,
|
||||||
|
Iter_Map, Iter_Filter, Iter_Fold, Iter_Any, Iter_All
|
||||||
|
};
|
||||||
|
import Std::Test::{
|
||||||
|
Test_AssertEqInt, Test_AssertTrue, Test_AssertEqString, Test_Pass
|
||||||
|
};
|
||||||
|
|
||||||
|
func Double(x: int) -> int {
|
||||||
|
return x * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsEven(x: int) -> bool {
|
||||||
|
return (x % 2) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
func IntToString(x: int) -> String {
|
||||||
|
return String_FromInt(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringLenAsInt(s: String) -> int {
|
||||||
|
return String_Len(s) as int;
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddLens(acc: int, s: String) -> int {
|
||||||
|
return acc + (String_Len(s) as int);
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddInt(acc: int, x: int) -> int {
|
||||||
|
return acc + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNonEmpty(s: String) -> bool {
|
||||||
|
return String_Len(s) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
var nums: Array<int> = Array_New<int>(8);
|
||||||
|
// Type args inferred from &nums : *Array<int> and value type
|
||||||
|
Array_Push(&nums, 1);
|
||||||
|
Array_Push(&nums, 2);
|
||||||
|
Array_Push(&nums, 3);
|
||||||
|
Array_Push(&nums, 4);
|
||||||
|
Array_Push(&nums, 5);
|
||||||
|
|
||||||
|
// Iter_Map<int,int> inferred from *Iter<int> + func(int)->int
|
||||||
|
let itA: Iter<int> = Array_Iter(&nums);
|
||||||
|
var doubled: Array<int> = Iter_Map(&itA, Double);
|
||||||
|
Test_AssertEqInt(Array_Get(&doubled, 0), 2);
|
||||||
|
Test_AssertEqInt(Array_Get(&doubled, 4), 10);
|
||||||
|
|
||||||
|
// Iter_Map<int,String>
|
||||||
|
let itB: Iter<int> = Array_Iter(&nums);
|
||||||
|
var asStr: Array<String> = Iter_Map(&itB, IntToString);
|
||||||
|
Test_AssertEqInt(Array_Len(&asStr) as int, 5);
|
||||||
|
Test_AssertEqString(Array_Get(&asStr, 0), "1");
|
||||||
|
Test_AssertEqString(Array_Get(&asStr, 4), "5");
|
||||||
|
|
||||||
|
// Iter_Map<String,int>
|
||||||
|
let itC: Iter<String> = Array_Iter(&asStr);
|
||||||
|
var lens: Array<int> = Iter_Map(&itC, StringLenAsInt);
|
||||||
|
Test_AssertEqInt(Array_Get(&lens, 0), 1);
|
||||||
|
|
||||||
|
// Iter_Filter<int>
|
||||||
|
let itD: Iter<int> = Array_Iter(&nums);
|
||||||
|
var evens: Array<int> = Iter_Filter(&itD, IsEven);
|
||||||
|
Test_AssertEqInt(Array_Len(&evens) as int, 2);
|
||||||
|
Test_AssertEqInt(Array_Get(&evens, 0), 2);
|
||||||
|
Test_AssertEqInt(Array_Get(&evens, 1), 4);
|
||||||
|
|
||||||
|
// Iter_Fold<String,int> — Acc from init, T from *Iter + func
|
||||||
|
let itE: Iter<String> = Array_Iter(&asStr);
|
||||||
|
let totalChars: int = Iter_Fold(&itE, 0, AddLens);
|
||||||
|
Test_AssertEqInt(totalChars, 5);
|
||||||
|
|
||||||
|
let itF: Iter<int> = Array_Iter(&nums);
|
||||||
|
let sum: int = Iter_Fold(&itF, 0, AddInt);
|
||||||
|
Test_AssertEqInt(sum, 15);
|
||||||
|
|
||||||
|
let itG: Iter<String> = Array_Iter(&asStr);
|
||||||
|
Test_AssertTrue(Iter_Any(&itG, IsNonEmpty));
|
||||||
|
|
||||||
|
PrintInt(sum);
|
||||||
|
PrintLine("");
|
||||||
|
Test_Pass("generic_infer_hof");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// Match arm guards: `p if cond => body`
|
||||||
|
// Bindings from the pattern are in scope for the guard expression.
|
||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Test::{Test_AssertEqInt, Test_Pass};
|
||||||
|
|
||||||
|
enum Option {
|
||||||
|
Some(int),
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
func Classify(n: int) -> int {
|
||||||
|
return match n {
|
||||||
|
x if x < 0 => -1,
|
||||||
|
x if x == 0 => 0,
|
||||||
|
x if x > 0 => 1,
|
||||||
|
_ => 99
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
// Ident + guard
|
||||||
|
Test_AssertEqInt(Classify(-5), -1);
|
||||||
|
Test_AssertEqInt(Classify(0), 0);
|
||||||
|
Test_AssertEqInt(Classify(7), 1);
|
||||||
|
|
||||||
|
// Literal + guard (second arm only when n is even)
|
||||||
|
let n: int = 4;
|
||||||
|
let a: int = match n {
|
||||||
|
4 if n % 2 == 0 => 40,
|
||||||
|
4 => 41,
|
||||||
|
_ => -1
|
||||||
|
};
|
||||||
|
Test_AssertEqInt(a, 40);
|
||||||
|
|
||||||
|
let m: int = 4;
|
||||||
|
let b: int = match m {
|
||||||
|
4 if m % 2 != 0 => 40,
|
||||||
|
4 => 41,
|
||||||
|
_ => -1
|
||||||
|
};
|
||||||
|
Test_AssertEqInt(b, 41);
|
||||||
|
|
||||||
|
// Range + guard
|
||||||
|
let k: int = 5;
|
||||||
|
let c: int = match k {
|
||||||
|
1..10 if k % 2 == 0 => 100,
|
||||||
|
1..10 => 101,
|
||||||
|
_ => -1
|
||||||
|
};
|
||||||
|
Test_AssertEqInt(c, 101);
|
||||||
|
|
||||||
|
let j: int = 6;
|
||||||
|
let d: int = match j {
|
||||||
|
1..10 if j % 2 == 0 => 100,
|
||||||
|
1..10 => 101,
|
||||||
|
_ => -1
|
||||||
|
};
|
||||||
|
Test_AssertEqInt(d, 100);
|
||||||
|
|
||||||
|
// Enum payload binding used in guard
|
||||||
|
let opt: Option = Option { tag: Option_Some };
|
||||||
|
opt.data.Some_0 = 15;
|
||||||
|
let e: int = match opt {
|
||||||
|
Option::Some(v) if v > 10 => v * 2,
|
||||||
|
Option::Some(v) => v,
|
||||||
|
Option::None => 0
|
||||||
|
};
|
||||||
|
Test_AssertEqInt(e, 30);
|
||||||
|
|
||||||
|
let small: Option = Option { tag: Option_Some };
|
||||||
|
small.data.Some_0 = 3;
|
||||||
|
let f: int = match small {
|
||||||
|
Option::Some(v) if v > 10 => v * 2,
|
||||||
|
Option::Some(v) => v,
|
||||||
|
Option::None => 0
|
||||||
|
};
|
||||||
|
Test_AssertEqInt(f, 3);
|
||||||
|
|
||||||
|
PrintInt(e);
|
||||||
|
PrintLine("");
|
||||||
|
Test_Pass("match_guards");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Test::{Test_AssertEqInt, Test_Pass};
|
||||||
|
|
||||||
|
func UncheckedInc(p: *int) {
|
||||||
|
*p = *p + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@[Checked]
|
||||||
|
func Inc(p: &mut int) {
|
||||||
|
*p = *p + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@[Checked]
|
||||||
|
func Get(p: &int) -> int {
|
||||||
|
return *p;
|
||||||
|
}
|
||||||
|
|
||||||
|
@[Checked]
|
||||||
|
func OkBorrow() -> int {
|
||||||
|
var x: int = 10;
|
||||||
|
Inc(&x);
|
||||||
|
return Get(&x);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
var n: int = 1;
|
||||||
|
UncheckedInc(&n);
|
||||||
|
Test_AssertEqInt(n, 2);
|
||||||
|
let r: int = OkBorrow();
|
||||||
|
Test_AssertEqInt(r, 11);
|
||||||
|
PrintInt(r);
|
||||||
|
PrintLine("");
|
||||||
|
Test_Pass("ownership_checked");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import Std::Test::{Test_AssertEqInt, Test_Pass};
|
||||||
|
import Std::Io::{PrintInt, PrintLine};
|
||||||
|
|
||||||
|
enum Option {
|
||||||
|
Some(int),
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let a: Option = Option { tag: Option_Some };
|
||||||
|
a.data.Some_0 = 10;
|
||||||
|
let b: Option = Option { tag: Option_Some };
|
||||||
|
b.data.Some_0 = 20;
|
||||||
|
|
||||||
|
// Same binding name `v` in two sequential matches
|
||||||
|
let x: int = match a {
|
||||||
|
Option::Some(v) => v + 1,
|
||||||
|
Option::None => 0
|
||||||
|
};
|
||||||
|
let y: int = match b {
|
||||||
|
Option::Some(v) => v + 2,
|
||||||
|
Option::None => 0
|
||||||
|
};
|
||||||
|
Test_AssertEqInt(x, 11);
|
||||||
|
Test_AssertEqInt(y, 22);
|
||||||
|
|
||||||
|
// Nested: outer and inner both bind `n`
|
||||||
|
let nested: int = match a {
|
||||||
|
Option::Some(n) => match b {
|
||||||
|
Option::Some(n) => n, // should be b's payload (20), not a's
|
||||||
|
Option::None => -1
|
||||||
|
},
|
||||||
|
Option::None => -2
|
||||||
|
};
|
||||||
|
Test_AssertEqInt(nested, 20);
|
||||||
|
|
||||||
|
// Shadow outer let with pattern binding
|
||||||
|
let v: int = 99;
|
||||||
|
let z: int = match a {
|
||||||
|
Option::Some(v) => v, // pattern v should be 10, not 99
|
||||||
|
Option::None => 0
|
||||||
|
};
|
||||||
|
Test_AssertEqInt(z, 10);
|
||||||
|
Test_AssertEqInt(v, 99); // outer v unchanged
|
||||||
|
|
||||||
|
PrintInt(nested);
|
||||||
|
PrintLine("");
|
||||||
|
Test_Pass("pat_shadow");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+3
-1
@@ -66,6 +66,7 @@ const pkRange: int = 3;
|
|||||||
const pkEnum: int = 4;
|
const pkEnum: int = 4;
|
||||||
const pkStruct: int = 5;
|
const pkStruct: int = 5;
|
||||||
const pkTuple: int = 6;
|
const pkTuple: int = 6;
|
||||||
|
const pkGuarded: int = 7; // `p if cond` — patChild1 = inner, patGuardExpr = condition
|
||||||
|
|
||||||
struct Pattern {
|
struct Pattern {
|
||||||
kind: int,
|
kind: int,
|
||||||
@@ -78,10 +79,11 @@ struct Pattern {
|
|||||||
patEnumPath: String, // for pkEnum: "Enum::Variant"
|
patEnumPath: String, // for pkEnum: "Enum::Variant"
|
||||||
patStructName: String, // for pkStruct (type name)
|
patStructName: String, // for pkStruct (type name)
|
||||||
patFieldName: String, // for struct field entry: field name in Point { x: a }
|
patFieldName: String, // for struct field entry: field name in Point { x: a }
|
||||||
patChild1: *Pattern, // range lo / nested
|
patChild1: *Pattern, // range lo / nested / guarded inner
|
||||||
patChild2: *Pattern, // range hi / nested
|
patChild2: *Pattern, // range hi / nested
|
||||||
patArgs: *Pattern, // pkEnum/pkTuple/pkStruct field list (head)
|
patArgs: *Pattern, // pkEnum/pkTuple/pkStruct field list (head)
|
||||||
patNext: *Pattern, // next sibling in patArgs list
|
patNext: *Pattern, // next sibling in patArgs list
|
||||||
|
patGuardExpr: *Expr, // for pkGuarded: the `if` condition
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match arm: pattern => body
|
// Match arm: pattern => body
|
||||||
|
|||||||
+74
-13
@@ -75,6 +75,8 @@ struct CEmitter {
|
|||||||
movedName5: String,
|
movedName5: String,
|
||||||
movedName6: String,
|
movedName6: String,
|
||||||
movedName7: String,
|
movedName7: String,
|
||||||
|
tmpCounter: int,
|
||||||
|
currentRetType: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
func CBE_PushDefer(cbe: *CEmitter, node: *HirNode) {
|
func CBE_PushDefer(cbe: *CEmitter, node: *HirNode) {
|
||||||
@@ -155,10 +157,8 @@ func CBE_GetAutoDropVarName(node: *HirNode) -> String {
|
|||||||
return varNode.strValue;
|
return varNode.strValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
func CBE_EmitDefers(cbe: *CEmitter) -> int {
|
// Emit one defer slot (shared by full-stack and scope-pop emitters).
|
||||||
if cbe.deferCount == 0 { return 0; }
|
func CBE_EmitOneDefer(cbe: *CEmitter, i: int) {
|
||||||
var i: int = cbe.deferCount - 1;
|
|
||||||
while i >= 0 {
|
|
||||||
var dn: *HirNode = null as *HirNode;
|
var dn: *HirNode = null as *HirNode;
|
||||||
if i == 0 { dn = cbe.defer0; }
|
if i == 0 { dn = cbe.defer0; }
|
||||||
if i == 1 { dn = cbe.defer1; }
|
if i == 1 { dn = cbe.defer1; }
|
||||||
@@ -171,8 +171,7 @@ func CBE_EmitDefers(cbe: *CEmitter) -> int {
|
|||||||
// Skip auto-drop for moved variables
|
// Skip auto-drop for moved variables
|
||||||
let deferVarName: String = CBE_GetAutoDropVarName(dn);
|
let deferVarName: String = CBE_GetAutoDropVarName(dn);
|
||||||
if !String_Eq(deferVarName, "") && CBE_IsMoved(cbe, deferVarName) {
|
if !String_Eq(deferVarName, "") && CBE_IsMoved(cbe, deferVarName) {
|
||||||
i = i - 1;
|
return;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
StringBuilder_Append(&cbe.sb, "\n");
|
StringBuilder_Append(&cbe.sb, "\n");
|
||||||
var sp: int = 0;
|
var sp: int = 0;
|
||||||
@@ -182,9 +181,32 @@ func CBE_EmitDefers(cbe: *CEmitter) -> int {
|
|||||||
}
|
}
|
||||||
CBE_EmitExpr(cbe, dn);
|
CBE_EmitExpr(cbe, dn);
|
||||||
StringBuilder_Append(&cbe.sb, ";");
|
StringBuilder_Append(&cbe.sb, ";");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit all active defers (LIFO) without clearing the stack.
|
||||||
|
// Must NOT clear: multiple return paths each need the full defer list.
|
||||||
|
// (Clearing caused Early(flag) { if (0) return; return 1 } to drop only on first exit.)
|
||||||
|
// Stack is reset at the start of each function emission.
|
||||||
|
func CBE_EmitDefers(cbe: *CEmitter) -> int {
|
||||||
|
if cbe.deferCount == 0 { return 0; }
|
||||||
|
var i: int = cbe.deferCount - 1;
|
||||||
|
while i >= 0 {
|
||||||
|
CBE_EmitOneDefer(cbe, i);
|
||||||
i = i - 1;
|
i = i - 1;
|
||||||
}
|
}
|
||||||
cbe.deferCount = 0;
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit branch/loop-local defers (indices fromIdx..count-1) then pop them.
|
||||||
|
// Outer defers stay live so sibling branches and later returns still drop correctly.
|
||||||
|
func CBE_EmitAndPopDefersFrom(cbe: *CEmitter, fromIdx: int) -> int {
|
||||||
|
if cbe.deferCount <= fromIdx { return 0; }
|
||||||
|
var i: int = cbe.deferCount - 1;
|
||||||
|
while i >= fromIdx {
|
||||||
|
CBE_EmitOneDefer(cbe, i);
|
||||||
|
i = i - 1;
|
||||||
|
}
|
||||||
|
cbe.deferCount = fromIdx;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,20 +403,48 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return
|
// Return — evaluate value first, then drop live locals, then return.
|
||||||
|
// (Emitting Drop before the value used to use-after-drop on `return a.id`.)
|
||||||
if kind == hReturn {
|
if kind == hReturn {
|
||||||
// Track moved variables via return
|
// Track moved variables via return (skip auto-drop of moved-out locals)
|
||||||
if node.child1 != null as *HirNode && node.child1.kind == hVar {
|
if node.child1 != null as *HirNode && node.child1.kind == hVar {
|
||||||
CBE_AddMoved(cbe, node.child1.strValue);
|
CBE_AddMoved(cbe, node.child1.strValue);
|
||||||
}
|
}
|
||||||
let hadDefers: int = CBE_EmitDefers(cbe);
|
if node.child1 != null as *HirNode && cbe.deferCount > 0 {
|
||||||
if hadDefers != 0 {
|
// Materialize into a temp so Drop cannot clobber the returned value.
|
||||||
|
// Prefer the enclosing function return type (field-access HIR often
|
||||||
|
// carries the base struct typeName, which is wrong for `return a.id`).
|
||||||
|
cbe.tmpCounter = cbe.tmpCounter + 1;
|
||||||
|
let tmpName: String = String_Concat("__retdrop_", String_FromInt(cbe.tmpCounter));
|
||||||
|
var retCt: String = "int";
|
||||||
|
if cbe.currentRetType != null as String && !String_Eq(cbe.currentRetType, "") && !String_Eq(cbe.currentRetType, "void") {
|
||||||
|
retCt = cbe.currentRetType;
|
||||||
|
} else if node.child1.typeKind != 0 {
|
||||||
|
retCt = CBackend_TypeToC(node.child1.typeKind);
|
||||||
|
}
|
||||||
|
StringBuilder_Append(&cbe.sb, CBE_CParamDecl(retCt, tmpName));
|
||||||
|
StringBuilder_Append(&cbe.sb, " = ");
|
||||||
|
CBE_EmitExpr(cbe, node.child1);
|
||||||
|
StringBuilder_Append(&cbe.sb, ";");
|
||||||
|
discard CBE_EmitDefers(cbe);
|
||||||
StringBuilder_Append(&cbe.sb, "\n");
|
StringBuilder_Append(&cbe.sb, "\n");
|
||||||
var sp: int = 0;
|
var sp: int = 0;
|
||||||
while sp < cbe.indent {
|
while sp < cbe.indent {
|
||||||
StringBuilder_Append(&cbe.sb, " ");
|
StringBuilder_Append(&cbe.sb, " ");
|
||||||
sp = sp + 1;
|
sp = sp + 1;
|
||||||
}
|
}
|
||||||
|
StringBuilder_Append(&cbe.sb, "return ");
|
||||||
|
StringBuilder_Append(&cbe.sb, tmpName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let hadDefers: int = CBE_EmitDefers(cbe);
|
||||||
|
if hadDefers != 0 {
|
||||||
|
StringBuilder_Append(&cbe.sb, "\n");
|
||||||
|
var sp2: int = 0;
|
||||||
|
while sp2 < cbe.indent {
|
||||||
|
StringBuilder_Append(&cbe.sb, " ");
|
||||||
|
sp2 = sp2 + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
StringBuilder_Append(&cbe.sb, "return");
|
StringBuilder_Append(&cbe.sb, "return");
|
||||||
if node.child1 != null as *HirNode {
|
if node.child1 != null as *HirNode {
|
||||||
@@ -444,15 +494,17 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If
|
// If — each branch has its own defer scope (locals do not leak)
|
||||||
if kind == hIf {
|
if kind == hIf {
|
||||||
|
|
||||||
StringBuilder_Append(&cbe.sb, "if (");
|
StringBuilder_Append(&cbe.sb, "if (");
|
||||||
CBE_EmitExpr(cbe, node.child1);
|
CBE_EmitExpr(cbe, node.child1);
|
||||||
StringBuilder_Append(&cbe.sb, ") {\n");
|
StringBuilder_Append(&cbe.sb, ") {\n");
|
||||||
if node.child2 != null as *HirNode {
|
if node.child2 != null as *HirNode {
|
||||||
|
let savedThen: int = cbe.deferCount;
|
||||||
cbe.indent = cbe.indent + 1;
|
cbe.indent = cbe.indent + 1;
|
||||||
CBE_EmitExpr(cbe, node.child2);
|
CBE_EmitExpr(cbe, node.child2);
|
||||||
|
discard CBE_EmitAndPopDefersFrom(cbe, savedThen);
|
||||||
cbe.indent = cbe.indent - 1;
|
cbe.indent = cbe.indent - 1;
|
||||||
}
|
}
|
||||||
var sp: int = 0;
|
var sp: int = 0;
|
||||||
@@ -464,8 +516,10 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
|||||||
let elseBlock: *HirNode = node.extraData as *HirNode;
|
let elseBlock: *HirNode = node.extraData as *HirNode;
|
||||||
if elseBlock != null as *HirNode {
|
if elseBlock != null as *HirNode {
|
||||||
StringBuilder_Append(&cbe.sb, " else {\n");
|
StringBuilder_Append(&cbe.sb, " else {\n");
|
||||||
|
let savedElse: int = cbe.deferCount;
|
||||||
cbe.indent = cbe.indent + 1;
|
cbe.indent = cbe.indent + 1;
|
||||||
CBE_EmitExpr(cbe, elseBlock);
|
CBE_EmitExpr(cbe, elseBlock);
|
||||||
|
discard CBE_EmitAndPopDefersFrom(cbe, savedElse);
|
||||||
cbe.indent = cbe.indent - 1;
|
cbe.indent = cbe.indent - 1;
|
||||||
sp = 0;
|
sp = 0;
|
||||||
while sp < cbe.indent {
|
while sp < cbe.indent {
|
||||||
@@ -479,14 +533,16 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// While
|
// While — loop-body locals dropped each iteration
|
||||||
if kind == hWhile {
|
if kind == hWhile {
|
||||||
StringBuilder_Append(&cbe.sb, "while (");
|
StringBuilder_Append(&cbe.sb, "while (");
|
||||||
CBE_EmitExpr(cbe, node.child1);
|
CBE_EmitExpr(cbe, node.child1);
|
||||||
StringBuilder_Append(&cbe.sb, ") {\n");
|
StringBuilder_Append(&cbe.sb, ") {\n");
|
||||||
if node.child2 != null as *HirNode {
|
if node.child2 != null as *HirNode {
|
||||||
|
let savedW: int = cbe.deferCount;
|
||||||
cbe.indent = cbe.indent + 1;
|
cbe.indent = cbe.indent + 1;
|
||||||
CBE_EmitExpr(cbe, node.child2);
|
CBE_EmitExpr(cbe, node.child2);
|
||||||
|
discard CBE_EmitAndPopDefersFrom(cbe, savedW);
|
||||||
cbe.indent = cbe.indent - 1;
|
cbe.indent = cbe.indent - 1;
|
||||||
}
|
}
|
||||||
var sp: int = 0;
|
var sp: int = 0;
|
||||||
@@ -502,8 +558,10 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
|||||||
if kind == hLoop {
|
if kind == hLoop {
|
||||||
StringBuilder_Append(&cbe.sb, "while (1) {\n");
|
StringBuilder_Append(&cbe.sb, "while (1) {\n");
|
||||||
if node.child1 != null as *HirNode {
|
if node.child1 != null as *HirNode {
|
||||||
|
let savedL: int = cbe.deferCount;
|
||||||
cbe.indent = cbe.indent + 1;
|
cbe.indent = cbe.indent + 1;
|
||||||
CBE_EmitExpr(cbe, node.child1);
|
CBE_EmitExpr(cbe, node.child1);
|
||||||
|
discard CBE_EmitAndPopDefersFrom(cbe, savedL);
|
||||||
cbe.indent = cbe.indent - 1;
|
cbe.indent = cbe.indent - 1;
|
||||||
}
|
}
|
||||||
var sp: int = 0;
|
var sp: int = 0;
|
||||||
@@ -1266,6 +1324,7 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
|||||||
cbe.mod = mod;
|
cbe.mod = mod;
|
||||||
cbe.deferCount = 0;
|
cbe.deferCount = 0;
|
||||||
cbe.movedCount = 0;
|
cbe.movedCount = 0;
|
||||||
|
cbe.tmpCounter = 0;
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
StringBuilder_Append(&cbe.sb, "// Generated by Bux C Backend v2\n");
|
StringBuilder_Append(&cbe.sb, "// Generated by Bux C Backend v2\n");
|
||||||
@@ -1566,6 +1625,8 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
|||||||
cbe.checkedFunc = mod.funcs[i].checkedFunc;
|
cbe.checkedFunc = mod.funcs[i].checkedFunc;
|
||||||
cbe.deferCount = 0;
|
cbe.deferCount = 0;
|
||||||
cbe.movedCount = 0;
|
cbe.movedCount = 0;
|
||||||
|
cbe.tmpCounter = 0;
|
||||||
|
cbe.currentRetType = mod.funcs[i].retTypeName;
|
||||||
var hasReturn: bool = false;
|
var hasReturn: bool = false;
|
||||||
cbe.indent = 1;
|
cbe.indent = 1;
|
||||||
CBE_EmitExpr(cbe, body);
|
CBE_EmitExpr(cbe, body);
|
||||||
|
|||||||
+365
-202
@@ -34,6 +34,101 @@ struct LowerCtx {
|
|||||||
// Borrow checker state
|
// Borrow checker state
|
||||||
checkedFunc: bool,
|
checkedFunc: bool,
|
||||||
releaseFunc: bool,
|
releaseFunc: bool,
|
||||||
|
// Pattern binding renames: source name → unique C local (shadowing-safe)
|
||||||
|
patMapCount: int,
|
||||||
|
patMapFrom0: String,
|
||||||
|
patMapTo0: String,
|
||||||
|
patMapFrom1: String,
|
||||||
|
patMapTo1: String,
|
||||||
|
patMapFrom2: String,
|
||||||
|
patMapTo2: String,
|
||||||
|
patMapFrom3: String,
|
||||||
|
patMapTo3: String,
|
||||||
|
patMapFrom4: String,
|
||||||
|
patMapTo4: String,
|
||||||
|
patMapFrom5: String,
|
||||||
|
patMapTo5: String,
|
||||||
|
patMapFrom6: String,
|
||||||
|
patMapTo6: String,
|
||||||
|
patMapFrom7: String,
|
||||||
|
patMapTo7: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lcx_PatLookup(ctx: *LowerCtx, src: String) -> String {
|
||||||
|
// Most recent rename wins (scan from end)
|
||||||
|
var i: int = ctx.patMapCount - 1;
|
||||||
|
while i >= 0 {
|
||||||
|
var from: String = "";
|
||||||
|
var to: String = "";
|
||||||
|
if i == 0 { from = ctx.patMapFrom0; to = ctx.patMapTo0; }
|
||||||
|
else if i == 1 { from = ctx.patMapFrom1; to = ctx.patMapTo1; }
|
||||||
|
else if i == 2 { from = ctx.patMapFrom2; to = ctx.patMapTo2; }
|
||||||
|
else if i == 3 { from = ctx.patMapFrom3; to = ctx.patMapTo3; }
|
||||||
|
else if i == 4 { from = ctx.patMapFrom4; to = ctx.patMapTo4; }
|
||||||
|
else if i == 5 { from = ctx.patMapFrom5; to = ctx.patMapTo5; }
|
||||||
|
else if i == 6 { from = ctx.patMapFrom6; to = ctx.patMapTo6; }
|
||||||
|
else if i == 7 { from = ctx.patMapFrom7; to = ctx.patMapTo7; }
|
||||||
|
if String_Eq(from, src) { return to; }
|
||||||
|
i = i - 1;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lcx_PatPush(ctx: *LowerCtx, src: String, dst: String) {
|
||||||
|
if ctx.patMapCount >= 8 { return; }
|
||||||
|
let i: int = ctx.patMapCount;
|
||||||
|
if i == 0 { ctx.patMapFrom0 = src; ctx.patMapTo0 = dst; }
|
||||||
|
else if i == 1 { ctx.patMapFrom1 = src; ctx.patMapTo1 = dst; }
|
||||||
|
else if i == 2 { ctx.patMapFrom2 = src; ctx.patMapTo2 = dst; }
|
||||||
|
else if i == 3 { ctx.patMapFrom3 = src; ctx.patMapTo3 = dst; }
|
||||||
|
else if i == 4 { ctx.patMapFrom4 = src; ctx.patMapTo4 = dst; }
|
||||||
|
else if i == 5 { ctx.patMapFrom5 = src; ctx.patMapTo5 = dst; }
|
||||||
|
else if i == 6 { ctx.patMapFrom6 = src; ctx.patMapTo6 = dst; }
|
||||||
|
else if i == 7 { ctx.patMapFrom7 = src; ctx.patMapTo7 = dst; }
|
||||||
|
ctx.patMapCount = ctx.patMapCount + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lcx_FreshPatName(ctx: *LowerCtx, src: String) -> String {
|
||||||
|
ctx.varCounter = ctx.varCounter + 1;
|
||||||
|
var safe: String = src;
|
||||||
|
if String_Eq(src, "") || String_Eq(src, "_") { safe = "x"; }
|
||||||
|
return String_Concat(String_Concat("__p", String_FromInt(ctx.varCounter as int64)),
|
||||||
|
String_Concat("_", safe));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alloca unique C local + store + scope define + rename map for pattern binding.
|
||||||
|
func Lcx_BindPatIdent(ctx: *LowerCtx, src: String, ty: String, value: *HirNode,
|
||||||
|
line: uint32, col: uint32) -> *HirNode {
|
||||||
|
if String_Eq(src, "") || String_Eq(src, "_") { return null as *HirNode; }
|
||||||
|
let cName: String = Lcx_FreshPatName(ctx, src);
|
||||||
|
Lcx_PatPush(ctx, src, cName);
|
||||||
|
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
alloca.kind = hAlloca;
|
||||||
|
alloca.line = line;
|
||||||
|
alloca.column = col;
|
||||||
|
alloca.strValue = cName;
|
||||||
|
alloca.typeName = ty;
|
||||||
|
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
store.kind = hStore;
|
||||||
|
store.line = line;
|
||||||
|
store.column = col;
|
||||||
|
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
v.kind = hVar;
|
||||||
|
v.strValue = cName;
|
||||||
|
store.child1 = v;
|
||||||
|
store.child2 = value;
|
||||||
|
alloca.child3 = store;
|
||||||
|
var bsym: Symbol;
|
||||||
|
bsym.kind = skVar;
|
||||||
|
bsym.name = src;
|
||||||
|
bsym.typeKind = tyInt;
|
||||||
|
bsym.typeName = ty;
|
||||||
|
bsym.refType = null as *TypeExpr;
|
||||||
|
bsym.isMutable = false;
|
||||||
|
bsym.isPublic = false;
|
||||||
|
bsym.decl = null as *Decl;
|
||||||
|
discard Scope_Define(ctx.scope, bsym);
|
||||||
|
return alloca;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -265,6 +360,27 @@ func Lcx_FindGenericStruct(ctx: *LowerCtx, name: String) -> *Decl {
|
|||||||
return null as *Decl;
|
return null as *Decl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract element type from mangled collection name: Array_int → int, Iter_String → String
|
||||||
|
func Lcx_ExtractElemFromName(typeName: String) -> String {
|
||||||
|
if String_Eq(typeName, "") { return ""; }
|
||||||
|
let len: uint = bux_strlen(typeName);
|
||||||
|
// "Array_" prefix (6 chars)
|
||||||
|
if len > 6 {
|
||||||
|
let p: String = bux_str_slice(typeName, 0, 6);
|
||||||
|
if String_Eq(p, "Array_") {
|
||||||
|
return bux_str_slice(typeName, 6, len - 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// "Iter_" prefix (5 chars)
|
||||||
|
if len > 5 {
|
||||||
|
let p: String = bux_str_slice(typeName, 0, 5);
|
||||||
|
if String_Eq(p, "Iter_") {
|
||||||
|
return bux_str_slice(typeName, 5, len - 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
func Lcx_MangleName(base: String, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
|
func Lcx_MangleName(base: String, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
|
||||||
let r: String = String_Concat(base, "_");
|
let r: String = String_Concat(base, "_");
|
||||||
r = String_Concat(r, typeArg0);
|
r = String_Concat(r, typeArg0);
|
||||||
@@ -487,6 +603,11 @@ func Lcx_PatternCond(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
|||||||
if pat == null as *Pattern { return null as *HirNode; }
|
if pat == null as *Pattern { return null as *HirNode; }
|
||||||
let kind: int = pat.kind;
|
let kind: int = pat.kind;
|
||||||
|
|
||||||
|
// Guarded: condition is only the inner pattern; guard applied after binds
|
||||||
|
if kind == pkGuarded {
|
||||||
|
return Lcx_PatternCond(ctx, subject, pat.patChild1, subjectEnumName, subjectHasData, line, col);
|
||||||
|
}
|
||||||
|
|
||||||
if kind == pkWildcard || kind == pkIdent {
|
if kind == pkWildcard || kind == pkIdent {
|
||||||
return null as *HirNode;
|
return null as *HirNode;
|
||||||
}
|
}
|
||||||
@@ -568,40 +689,16 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
|||||||
subjectEnumName: String, subjectHasData: bool,
|
subjectEnumName: String, subjectHasData: bool,
|
||||||
line: uint32, col: uint32) -> *HirNode {
|
line: uint32, col: uint32) -> *HirNode {
|
||||||
if pat == null as *Pattern { return null as *HirNode; }
|
if pat == null as *Pattern { return null as *HirNode; }
|
||||||
|
// Guarded: bind from inner pattern
|
||||||
|
if pat.kind == pkGuarded {
|
||||||
|
return Lcx_PatternBindings(ctx, subject, pat.patChild1, subjectEnumName, subjectHasData, line, col);
|
||||||
|
}
|
||||||
if pat.kind == pkIdent {
|
if pat.kind == pkIdent {
|
||||||
// `_` is wildcard, not a binding
|
|
||||||
if String_Eq(pat.patIdent, "_") { return null as *HirNode; }
|
|
||||||
let ty: String = "int";
|
let ty: String = "int";
|
||||||
if subject != null as *HirNode && !String_Eq(subject.typeName, "") {
|
if subject != null as *HirNode && !String_Eq(subject.typeName, "") {
|
||||||
ty = subject.typeName;
|
ty = subject.typeName;
|
||||||
}
|
}
|
||||||
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
return Lcx_BindPatIdent(ctx, pat.patIdent, ty, subject, line, col);
|
||||||
alloca.kind = hAlloca;
|
|
||||||
alloca.line = line;
|
|
||||||
alloca.column = col;
|
|
||||||
alloca.strValue = pat.patIdent;
|
|
||||||
alloca.typeName = ty;
|
|
||||||
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
||||||
store.kind = hStore;
|
|
||||||
store.line = line;
|
|
||||||
store.column = col;
|
|
||||||
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
||||||
v.kind = hVar;
|
|
||||||
v.strValue = pat.patIdent;
|
|
||||||
store.child1 = v;
|
|
||||||
store.child2 = subject;
|
|
||||||
alloca.child3 = store;
|
|
||||||
var bsym: Symbol;
|
|
||||||
bsym.kind = skVar;
|
|
||||||
bsym.name = pat.patIdent;
|
|
||||||
bsym.typeKind = tyInt;
|
|
||||||
bsym.typeName = ty;
|
|
||||||
bsym.refType = null as *TypeExpr;
|
|
||||||
bsym.isMutable = false;
|
|
||||||
bsym.isPublic = false;
|
|
||||||
bsym.decl = null as *Decl;
|
|
||||||
discard Scope_Define(ctx.scope, bsym);
|
|
||||||
return alloca;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tuple: (a, b) → a = subject._0; b = subject._1
|
// Tuple: (a, b) → a = subject._0; b = subject._1
|
||||||
@@ -629,38 +726,16 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
|||||||
fLoad.column = col;
|
fLoad.column = col;
|
||||||
fLoad.child1 = fPtr;
|
fLoad.child1 = fPtr;
|
||||||
fLoad.typeName = "int";
|
fLoad.typeName = "int";
|
||||||
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
let bound: *HirNode = Lcx_BindPatIdent(ctx, elem.patIdent, "int", fLoad, line, col);
|
||||||
alloca.kind = hAlloca;
|
if bound != null as *HirNode {
|
||||||
alloca.line = line;
|
|
||||||
alloca.column = col;
|
|
||||||
alloca.strValue = elem.patIdent;
|
|
||||||
alloca.typeName = "int";
|
|
||||||
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
||||||
store.kind = hStore;
|
|
||||||
store.line = line;
|
|
||||||
store.column = col;
|
|
||||||
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
||||||
v.kind = hVar;
|
|
||||||
v.strValue = elem.patIdent;
|
|
||||||
store.child1 = v;
|
|
||||||
store.child2 = fLoad;
|
|
||||||
alloca.child3 = store;
|
|
||||||
var bsym: Symbol;
|
|
||||||
bsym.kind = skVar;
|
|
||||||
bsym.name = elem.patIdent;
|
|
||||||
bsym.typeKind = tyInt;
|
|
||||||
bsym.typeName = "int";
|
|
||||||
bsym.refType = null as *TypeExpr;
|
|
||||||
bsym.isMutable = false;
|
|
||||||
bsym.isPublic = false;
|
|
||||||
bsym.decl = null as *Decl;
|
|
||||||
discard Scope_Define(ctx.scope, bsym);
|
|
||||||
if head == null as *HirNode {
|
if head == null as *HirNode {
|
||||||
head = alloca;
|
head = bound;
|
||||||
tail = store;
|
tail = bound;
|
||||||
|
while tail.child3 != null as *HirNode { tail = tail.child3; }
|
||||||
} else {
|
} else {
|
||||||
tail.child3 = alloca;
|
tail.child3 = bound;
|
||||||
tail = store;
|
while tail.child3 != null as *HirNode { tail = tail.child3; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elem = elem.patNext;
|
elem = elem.patNext;
|
||||||
@@ -690,38 +765,16 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
|||||||
fLoad.column = col;
|
fLoad.column = col;
|
||||||
fLoad.child1 = fPtr;
|
fLoad.child1 = fPtr;
|
||||||
fLoad.typeName = "int";
|
fLoad.typeName = "int";
|
||||||
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
let bound: *HirNode = Lcx_BindPatIdent(ctx, field.patIdent, "int", fLoad, line, col);
|
||||||
alloca.kind = hAlloca;
|
if bound != null as *HirNode {
|
||||||
alloca.line = line;
|
|
||||||
alloca.column = col;
|
|
||||||
alloca.strValue = field.patIdent;
|
|
||||||
alloca.typeName = "int";
|
|
||||||
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
||||||
store.kind = hStore;
|
|
||||||
store.line = line;
|
|
||||||
store.column = col;
|
|
||||||
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
||||||
v.kind = hVar;
|
|
||||||
v.strValue = field.patIdent;
|
|
||||||
store.child1 = v;
|
|
||||||
store.child2 = fLoad;
|
|
||||||
alloca.child3 = store;
|
|
||||||
var bsym: Symbol;
|
|
||||||
bsym.kind = skVar;
|
|
||||||
bsym.name = field.patIdent;
|
|
||||||
bsym.typeKind = tyInt;
|
|
||||||
bsym.typeName = "int";
|
|
||||||
bsym.refType = null as *TypeExpr;
|
|
||||||
bsym.isMutable = false;
|
|
||||||
bsym.isPublic = false;
|
|
||||||
bsym.decl = null as *Decl;
|
|
||||||
discard Scope_Define(ctx.scope, bsym);
|
|
||||||
if head == null as *HirNode {
|
if head == null as *HirNode {
|
||||||
head = alloca;
|
head = bound;
|
||||||
tail = store;
|
tail = bound;
|
||||||
|
while tail.child3 != null as *HirNode { tail = tail.child3; }
|
||||||
} else {
|
} else {
|
||||||
tail.child3 = alloca;
|
tail.child3 = bound;
|
||||||
tail = store;
|
while tail.child3 != null as *HirNode { tail = tail.child3; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
field = field.patNext;
|
field = field.patNext;
|
||||||
@@ -827,41 +880,16 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
|||||||
fLoad.typeName = ftype;
|
fLoad.typeName = ftype;
|
||||||
|
|
||||||
if arg.kind == pkIdent && !String_Eq(arg.patIdent, "_") {
|
if arg.kind == pkIdent && !String_Eq(arg.patIdent, "_") {
|
||||||
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
let bound: *HirNode = Lcx_BindPatIdent(ctx, arg.patIdent, ftype, fLoad, line, col);
|
||||||
alloca.kind = hAlloca;
|
if bound != null as *HirNode {
|
||||||
alloca.line = line;
|
|
||||||
alloca.column = col;
|
|
||||||
alloca.strValue = arg.patIdent;
|
|
||||||
alloca.typeName = ftype;
|
|
||||||
|
|
||||||
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
||||||
store.kind = hStore;
|
|
||||||
store.line = line;
|
|
||||||
store.column = col;
|
|
||||||
let bv: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
||||||
bv.kind = hVar;
|
|
||||||
bv.strValue = arg.patIdent;
|
|
||||||
store.child1 = bv;
|
|
||||||
store.child2 = fLoad;
|
|
||||||
alloca.child3 = store;
|
|
||||||
|
|
||||||
var bsym: Symbol;
|
|
||||||
bsym.kind = skVar;
|
|
||||||
bsym.name = arg.patIdent;
|
|
||||||
bsym.typeKind = tyInt;
|
|
||||||
bsym.typeName = ftype;
|
|
||||||
bsym.refType = null as *TypeExpr;
|
|
||||||
bsym.isMutable = false;
|
|
||||||
bsym.isPublic = false;
|
|
||||||
bsym.decl = null as *Decl;
|
|
||||||
discard Scope_Define(ctx.scope, bsym);
|
|
||||||
|
|
||||||
if head == null as *HirNode {
|
if head == null as *HirNode {
|
||||||
head = alloca;
|
head = bound;
|
||||||
tail = store;
|
tail = bound;
|
||||||
|
while tail.child3 != null as *HirNode { tail = tail.child3; }
|
||||||
} else {
|
} else {
|
||||||
tail.child3 = alloca;
|
tail.child3 = bound;
|
||||||
tail = store;
|
while tail.child3 != null as *HirNode { tail = tail.child3; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if arg.kind == pkTuple || arg.kind == pkStruct || arg.kind == pkEnum {
|
} else if arg.kind == pkTuple || arg.kind == pkStruct || arg.kind == pkEnum {
|
||||||
// Nested pattern on payload field
|
// Nested pattern on payload field
|
||||||
@@ -913,7 +941,9 @@ func Lcx_AppendToChain(head: *HirNode, node: *HirNode) {
|
|||||||
cur.child3 = node;
|
cur.child3 = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lower match expr → hBlock: alloca result; if-else stores; strValue = result name
|
// Lower match expr → sequential ifs with a found flag (no shared HIR DAG).
|
||||||
|
// Each arm: if (!found) { if (cond) { binds; if (guard) { result=body; found=true; } } }
|
||||||
|
// Guards see pattern bindings. Supports pkGuarded (`p if cond`).
|
||||||
func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
||||||
let line: uint32 = expr.line;
|
let line: uint32 = expr.line;
|
||||||
let col: uint32 = expr.column;
|
let col: uint32 = expr.column;
|
||||||
@@ -921,6 +951,8 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
|
|
||||||
ctx.varCounter = ctx.varCounter + 1;
|
ctx.varCounter = ctx.varCounter + 1;
|
||||||
let resultName: String = String_Concat("__match_", String_FromInt(ctx.varCounter as int64));
|
let resultName: String = String_Concat("__match_", String_FromInt(ctx.varCounter as int64));
|
||||||
|
ctx.varCounter = ctx.varCounter + 1;
|
||||||
|
let foundName: String = String_Concat("__found_", String_FromInt(ctx.varCounter as int64));
|
||||||
|
|
||||||
// Result type from sema refType, default int
|
// Result type from sema refType, default int
|
||||||
var typeName: String = "int";
|
var typeName: String = "int";
|
||||||
@@ -938,7 +970,7 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alloca result
|
// Alloca result + found flag
|
||||||
let allocaNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
let allocaNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
allocaNode.kind = hAlloca;
|
allocaNode.kind = hAlloca;
|
||||||
allocaNode.line = line;
|
allocaNode.line = line;
|
||||||
@@ -946,35 +978,48 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
allocaNode.strValue = resultName;
|
allocaNode.strValue = resultName;
|
||||||
allocaNode.typeName = typeName;
|
allocaNode.typeName = typeName;
|
||||||
|
|
||||||
// Collect arms into a temporary array via reverse build of if-chain
|
let foundAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
// First count arms and build from last to first
|
foundAlloca.kind = hAlloca;
|
||||||
var armCount: int = 0;
|
foundAlloca.line = line;
|
||||||
var arm: *MatchArm = expr.matchArms;
|
foundAlloca.column = col;
|
||||||
while arm != null as *MatchArm {
|
foundAlloca.strValue = foundName;
|
||||||
armCount = armCount + 1;
|
foundAlloca.typeName = "bool";
|
||||||
arm = arm.next;
|
allocaNode.child3 = foundAlloca;
|
||||||
}
|
|
||||||
|
|
||||||
// Build if-else from last arm to first
|
let foundInit: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
var ifChain: *HirNode = null as *HirNode;
|
foundInit.kind = hStore;
|
||||||
var ai: int = armCount - 1;
|
foundInit.line = line;
|
||||||
while ai >= 0 {
|
foundInit.column = col;
|
||||||
// Find arm at index ai
|
let foundVar0: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
foundVar0.kind = hVar;
|
||||||
|
foundVar0.strValue = foundName;
|
||||||
|
foundInit.child1 = foundVar0;
|
||||||
|
foundInit.child2 = Lcx_MakeLitHir(tkBoolLiteral, "false", line, col);
|
||||||
|
foundAlloca.child3 = foundInit;
|
||||||
|
|
||||||
|
// Chain of arm ifs (forward order), linked via child3
|
||||||
|
var tail: *HirNode = foundInit;
|
||||||
var cur: *MatchArm = expr.matchArms;
|
var cur: *MatchArm = expr.matchArms;
|
||||||
var j: int = 0;
|
while cur != null as *MatchArm {
|
||||||
while j < ai && cur != null as *MatchArm {
|
// Snapshot rename map so this arm's bindings don't leak to later arms
|
||||||
cur = cur.next;
|
let savedMapCount: int = ctx.patMapCount;
|
||||||
j = j + 1;
|
var bindPat: *Pattern = cur.pattern;
|
||||||
|
var guardExpr: *Expr = null as *Expr;
|
||||||
|
if cur.pattern != null as *Pattern && cur.pattern.kind == pkGuarded {
|
||||||
|
bindPat = cur.pattern.patChild1;
|
||||||
|
guardExpr = cur.pattern.patGuardExpr;
|
||||||
}
|
}
|
||||||
if cur == null as *MatchArm {
|
// Bindings before guard/body so renames are active and allocas precede uses
|
||||||
ai = ai - 1;
|
let bindHead: *HirNode = Lcx_PatternBindings(ctx, subject, bindPat, subjectEnumName, subjectHasData, line, col);
|
||||||
continue;
|
var guardHirEarly: *HirNode = null as *HirNode;
|
||||||
|
if guardExpr != null as *Expr {
|
||||||
|
guardHirEarly = Lcx_LowerExpr(ctx, guardExpr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pattern bindings before body (so body idents resolve)
|
|
||||||
let bindHead: *HirNode = Lcx_PatternBindings(ctx, subject, cur.pattern, subjectEnumName, subjectHasData, line, col);
|
|
||||||
let bodyHir: *HirNode = Lcx_LowerExpr(ctx, cur.body);
|
let bodyHir: *HirNode = Lcx_LowerExpr(ctx, cur.body);
|
||||||
// result = body (expand block/match yield: run stmts then store result var)
|
// Pop this arm's renames (nested matches already restored themselves)
|
||||||
|
ctx.patMapCount = savedMapCount;
|
||||||
|
|
||||||
|
// store result = body
|
||||||
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
storeNode.kind = hStore;
|
storeNode.kind = hStore;
|
||||||
storeNode.line = line;
|
storeNode.line = line;
|
||||||
@@ -992,58 +1037,94 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
storeNode.child2 = bodyHir;
|
storeNode.child2 = bodyHir;
|
||||||
}
|
}
|
||||||
|
|
||||||
// armBlock = bindings → body stmts → store
|
// found = true
|
||||||
let armBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
let foundSet: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
armBlock.kind = hBlock;
|
foundSet.kind = hStore;
|
||||||
armBlock.line = line;
|
foundSet.line = line;
|
||||||
armBlock.column = col;
|
foundSet.column = col;
|
||||||
var chainHead: *HirNode = bindHead;
|
let foundVar1: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
if chainHead == null as *HirNode {
|
foundVar1.kind = hVar;
|
||||||
chainHead = bodyPrefix;
|
foundVar1.strValue = foundName;
|
||||||
} else if bodyPrefix != null as *HirNode {
|
foundSet.child1 = foundVar1;
|
||||||
var bt0: *HirNode = chainHead;
|
foundSet.child2 = Lcx_MakeLitHir(tkBoolLiteral, "true", line, col);
|
||||||
while bt0.child3 != null as *HirNode { bt0 = bt0.child3; }
|
storeNode.child3 = foundSet;
|
||||||
bt0.child3 = bodyPrefix;
|
|
||||||
}
|
// success block: bodyPrefix → store → found=true
|
||||||
if chainHead != null as *HirNode {
|
let successBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
armBlock.child1 = chainHead;
|
successBlock.kind = hBlock;
|
||||||
var bt: *HirNode = chainHead;
|
successBlock.line = line;
|
||||||
while bt.child3 != null as *HirNode { bt = bt.child3; }
|
successBlock.column = col;
|
||||||
bt.child3 = storeNode;
|
if bodyPrefix != null as *HirNode {
|
||||||
|
successBlock.child1 = bodyPrefix;
|
||||||
|
var sbt: *HirNode = bodyPrefix;
|
||||||
|
while sbt.child3 != null as *HirNode { sbt = sbt.child3; }
|
||||||
|
sbt.child3 = storeNode;
|
||||||
} else {
|
} else {
|
||||||
armBlock.child1 = storeNode;
|
successBlock.child1 = storeNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cond: *HirNode = Lcx_PatternCond(ctx, subject, cur.pattern, subjectEnumName, subjectHasData, line, col);
|
// Optional guard wraps success (already lowered with renames active)
|
||||||
if cond == null as *HirNode {
|
var afterBinds: *HirNode = successBlock;
|
||||||
// Always-true arm
|
if guardHirEarly != null as *HirNode {
|
||||||
if ifChain == null as *HirNode {
|
let guardIf: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
ifChain = armBlock;
|
guardIf.kind = hIf;
|
||||||
} else {
|
guardIf.line = line;
|
||||||
let ifNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
guardIf.column = col;
|
||||||
ifNode.kind = hIf;
|
guardIf.child1 = guardHirEarly;
|
||||||
ifNode.line = line;
|
guardIf.child2 = successBlock;
|
||||||
ifNode.column = col;
|
afterBinds = guardIf;
|
||||||
ifNode.child1 = Lcx_MakeTrueHir(line, col);
|
|
||||||
ifNode.child2 = armBlock;
|
|
||||||
ifNode.extraData = ifChain as *void;
|
|
||||||
ifChain = ifNode;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let ifNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
||||||
ifNode.kind = hIf;
|
|
||||||
ifNode.line = line;
|
|
||||||
ifNode.column = col;
|
|
||||||
ifNode.child1 = cond;
|
|
||||||
ifNode.child2 = armBlock;
|
|
||||||
ifNode.extraData = ifChain as *void;
|
|
||||||
ifChain = ifNode;
|
|
||||||
}
|
|
||||||
ai = ai - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chain: alloca → ifChain
|
// armInner: binds → afterBinds
|
||||||
allocaNode.child3 = ifChain;
|
let armInner: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
armInner.kind = hBlock;
|
||||||
|
armInner.line = line;
|
||||||
|
armInner.column = col;
|
||||||
|
if bindHead != null as *HirNode {
|
||||||
|
armInner.child1 = bindHead;
|
||||||
|
var ibt: *HirNode = bindHead;
|
||||||
|
while ibt.child3 != null as *HirNode { ibt = ibt.child3; }
|
||||||
|
ibt.child3 = afterBinds;
|
||||||
|
} else {
|
||||||
|
armInner.child1 = afterBinds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional pattern condition
|
||||||
|
let cond: *HirNode = Lcx_PatternCond(ctx, subject, bindPat, subjectEnumName, subjectHasData, line, col);
|
||||||
|
var armBody: *HirNode = armInner;
|
||||||
|
if cond != null as *HirNode {
|
||||||
|
let condIf: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
condIf.kind = hIf;
|
||||||
|
condIf.line = line;
|
||||||
|
condIf.column = col;
|
||||||
|
condIf.child1 = cond;
|
||||||
|
condIf.child2 = armInner;
|
||||||
|
armBody = condIf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (!found) armBody
|
||||||
|
let foundLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
foundLoad.kind = hVar;
|
||||||
|
foundLoad.strValue = foundName;
|
||||||
|
foundLoad.typeName = "bool";
|
||||||
|
let notFound: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
notFound.kind = hUnary;
|
||||||
|
notFound.line = line;
|
||||||
|
notFound.column = col;
|
||||||
|
notFound.intValue = tkBang;
|
||||||
|
notFound.child1 = foundLoad;
|
||||||
|
|
||||||
|
let tryIf: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
tryIf.kind = hIf;
|
||||||
|
tryIf.line = line;
|
||||||
|
tryIf.column = col;
|
||||||
|
tryIf.child1 = notFound;
|
||||||
|
tryIf.child2 = armBody;
|
||||||
|
|
||||||
|
tail.child3 = tryIf;
|
||||||
|
tail = tryIf;
|
||||||
|
cur = cur.next;
|
||||||
|
}
|
||||||
|
|
||||||
let block: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
let block: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
block.kind = hBlock;
|
block.kind = hBlock;
|
||||||
@@ -1153,6 +1234,18 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
|
|
||||||
// Identifier → variable reference
|
// Identifier → variable reference
|
||||||
if kind == ekIdent {
|
if kind == ekIdent {
|
||||||
|
// Pattern binding rename: source name → unique C local
|
||||||
|
let ren: String = Lcx_PatLookup(ctx, expr.strValue);
|
||||||
|
if !String_Eq(ren, "") {
|
||||||
|
n.kind = hVar;
|
||||||
|
n.strValue = ren;
|
||||||
|
let rsym: Symbol = Scope_Lookup(ctx.scope, expr.strValue);
|
||||||
|
n.typeKind = rsym.typeKind;
|
||||||
|
if rsym.typeName != null as String && !String_Eq(rsym.typeName, "") {
|
||||||
|
n.typeName = rsym.typeName;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
// Capture rewriting: if inside closure body and this ident is captured,
|
// Capture rewriting: if inside closure body and this ident is captured,
|
||||||
// emit field access on env instance instead of bare variable
|
// emit field access on env instance instead of bare variable
|
||||||
if ctx.closureDepth > 0 && ctx.currentClosureExpr != null as *Expr && !String_Eq(ctx.envInstanceName, "") {
|
if ctx.closureDepth > 0 && ctx.currentClosureExpr != null as *Expr && !String_Eq(ctx.envInstanceName, "") {
|
||||||
@@ -1564,20 +1657,67 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
n.kind = hCall;
|
n.kind = hCall;
|
||||||
n.strValue = expr.child1.strValue;
|
n.strValue = expr.child1.strValue;
|
||||||
|
|
||||||
// Generic call monomorphization
|
// Generic call monomorphization (explicit / inferred type args)
|
||||||
if expr.child1 != null as *Expr && expr.child1.genericTypeArgCount > 0 {
|
if expr.child1 != null as *Expr {
|
||||||
let genDecl: *Decl = Lcx_FindGenericFunc(ctx, expr.child1.strValue);
|
var argc: int = expr.child1.genericTypeArgCount;
|
||||||
if genDecl != null as *Decl {
|
|
||||||
var typeArg0: String = expr.child1.genericTypeArg0;
|
var typeArg0: String = expr.child1.genericTypeArg0;
|
||||||
var typeArg1: String = expr.child1.genericTypeArg1;
|
var typeArg1: String = expr.child1.genericTypeArg1;
|
||||||
|
// Fallback: infer T from first *Array/*Iter arg when sema left count=0
|
||||||
|
if argc == 0 {
|
||||||
|
let genTry: *Decl = Lcx_FindGenericFunc(ctx, expr.child1.strValue);
|
||||||
|
if genTry != null as *Decl && genTry.typeParamCount > 0 {
|
||||||
|
if expr.callArgs != null as *ExprList && expr.callArgs.expr != null as *Expr {
|
||||||
|
var a0: *Expr = expr.callArgs.expr;
|
||||||
|
var te: *TypeExpr = a0.refType;
|
||||||
|
if te == null as *TypeExpr && a0.kind == ekUnary && a0.intValue == tkAmp {
|
||||||
|
if a0.child1 != null as *Expr { te = a0.child1.refType; }
|
||||||
|
}
|
||||||
|
if te != null as *TypeExpr {
|
||||||
|
// Unwrap pointer
|
||||||
|
if (te.kind == tekPointer || te.kind == tekRef || te.kind == tekMutRef)
|
||||||
|
&& te.pointerPointee != null as *TypeExpr {
|
||||||
|
te = te.pointerPointee;
|
||||||
|
}
|
||||||
|
var elem: String = "";
|
||||||
|
if te.typeArgCount > 0 {
|
||||||
|
elem = te.typeArgName0;
|
||||||
|
} else {
|
||||||
|
// Mangled Array_int / Iter_String
|
||||||
|
elem = Lcx_ExtractElemFromName(te.typeName);
|
||||||
|
}
|
||||||
|
if !String_Eq(elem, "") {
|
||||||
|
typeArg0 = elem;
|
||||||
|
argc = 1;
|
||||||
|
// Second type arg from func-typed second argument if needed
|
||||||
|
if genTry.typeParamCount >= 2 && expr.callArgs.next != null as *ExprList {
|
||||||
|
let a1e: *Expr = expr.callArgs.next.expr;
|
||||||
|
if a1e != null as *Expr && a1e.kind == ekIdent {
|
||||||
|
let fsym: Symbol = Scope_Lookup(ctx.scope, a1e.strValue);
|
||||||
|
if fsym.kind == skFunc && fsym.decl != null as *Decl
|
||||||
|
&& fsym.decl.retType != null as *TypeExpr
|
||||||
|
&& fsym.decl.retType.kind == tekNamed {
|
||||||
|
typeArg1 = fsym.decl.retType.typeName;
|
||||||
|
argc = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if argc > 0 {
|
||||||
|
let genDecl: *Decl = Lcx_FindGenericFunc(ctx, expr.child1.strValue);
|
||||||
|
if genDecl != null as *Decl {
|
||||||
if String_Eq(typeArg0, ctx.substParam0) { typeArg0 = ctx.substArg0; }
|
if String_Eq(typeArg0, ctx.substParam0) { typeArg0 = ctx.substArg0; }
|
||||||
if String_Eq(typeArg0, ctx.substParam1) { typeArg0 = ctx.substArg1; }
|
if String_Eq(typeArg0, ctx.substParam1) { typeArg0 = ctx.substArg1; }
|
||||||
if String_Eq(typeArg1, ctx.substParam0) { typeArg1 = ctx.substArg0; }
|
if String_Eq(typeArg1, ctx.substParam0) { typeArg1 = ctx.substArg0; }
|
||||||
if String_Eq(typeArg1, ctx.substParam1) { typeArg1 = ctx.substArg1; }
|
if String_Eq(typeArg1, ctx.substParam1) { typeArg1 = ctx.substArg1; }
|
||||||
let mangled: String = Lcx_GenerateFuncInstance(ctx, genDecl, typeArg0, typeArg1, expr.child1.genericTypeArgCount);
|
let mangled: String = Lcx_GenerateFuncInstance(ctx, genDecl, typeArg0, typeArg1, argc);
|
||||||
n.strValue = mangled;
|
n.strValue = mangled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Lower arguments into child1/child2/extraData
|
// Lower arguments into child1/child2/extraData
|
||||||
var arg: *ExprList = expr.callArgs;
|
var arg: *ExprList = expr.callArgs;
|
||||||
@@ -2476,10 +2616,33 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
|
|||||||
|
|
||||||
// Return
|
// Return
|
||||||
if kind == skReturn {
|
if kind == skReturn {
|
||||||
n.kind = hReturn;
|
|
||||||
if stmt.child1 != null as *Expr {
|
if stmt.child1 != null as *Expr {
|
||||||
n.child1 = Lcx_LowerExpr(ctx, stmt.child1);
|
let retVal: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
|
||||||
|
// `return match { ... }` / other multi-stmt yields: expand stmts then return result var
|
||||||
|
if retVal != null as *HirNode && Lcx_IsMatchYield(retVal) {
|
||||||
|
let retVar: *HirNode = Lcx_YieldVarOf(retVal);
|
||||||
|
let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
retNode.kind = hReturn;
|
||||||
|
retNode.line = line;
|
||||||
|
retNode.column = col;
|
||||||
|
retNode.child1 = retVar;
|
||||||
|
retVal.strValue = "";
|
||||||
|
var lastIn: *HirNode = retVal.child1;
|
||||||
|
if lastIn == null as *HirNode {
|
||||||
|
retVal.child1 = retNode;
|
||||||
|
} else {
|
||||||
|
while lastIn.child3 != null as *HirNode {
|
||||||
|
lastIn = lastIn.child3;
|
||||||
}
|
}
|
||||||
|
lastIn.child3 = retNode;
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
n.kind = hReturn;
|
||||||
|
n.child1 = retVal;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
n.kind = hReturn;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+34
-2
@@ -58,14 +58,27 @@ func parserPeek(p: *Parser, ahead: int) -> int {
|
|||||||
return tkEndOfFile;
|
return tkEndOfFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookahead to determine if '<' starts a type argument list.
|
// Lookahead to determine if '<' starts a type argument list (`Foo<int>`).
|
||||||
|
// Must not treat value comparisons `x < 0` as generics when a later `x > 0`
|
||||||
|
// exists (e.g. multiple match arm guards).
|
||||||
func parserIsTypeArgListAhead(p: *Parser) -> bool {
|
func parserIsTypeArgListAhead(p: *Parser) -> bool {
|
||||||
if !parserCheck(p, tkLt) { return false; }
|
if !parserCheck(p, tkLt) { return false; }
|
||||||
var depth: int = 0;
|
var depth: int = 0;
|
||||||
var ahead: int = 0;
|
var ahead: int = 0;
|
||||||
while true {
|
while true {
|
||||||
let kind: int = parserPeek(p, ahead);
|
let kind: int = parserPeek(p, ahead);
|
||||||
if kind == tkEndOfFile || kind == tkLBrace || kind == tkSemicolon {
|
// Hard stops: cannot appear inside <...> type args
|
||||||
|
if kind == tkEndOfFile || kind == tkLBrace || kind == tkRBrace || kind == tkSemicolon
|
||||||
|
|| kind == tkFatArrow || kind == tkIf || kind == tkElse || kind == tkWhile
|
||||||
|
|| kind == tkFor || kind == tkMatch || kind == tkReturn || kind == tkLet || kind == tkVar
|
||||||
|
|| kind == tkEq || kind == tkNe || kind == tkLe || kind == tkGe
|
||||||
|
|| kind == tkAmpAmp || kind == tkPipePipe || kind == tkAssign {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Literals / arithmetic ⇒ value expression, not type args
|
||||||
|
if kind == tkIntLiteral || kind == tkFloatLiteral || kind == tkStringLiteral
|
||||||
|
|| kind == tkCharLiteral || kind == tkBoolLiteral
|
||||||
|
|| kind == tkPlus || kind == tkMinus || kind == tkSlash || kind == tkPercent {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if kind == tkLt {
|
if kind == tkLt {
|
||||||
@@ -729,6 +742,7 @@ func parserMakePattern(kind: int, line: uint32, col: uint32) -> *Pattern {
|
|||||||
pat.patChild2 = null as *Pattern;
|
pat.patChild2 = null as *Pattern;
|
||||||
pat.patArgs = null as *Pattern;
|
pat.patArgs = null as *Pattern;
|
||||||
pat.patNext = null as *Pattern;
|
pat.patNext = null as *Pattern;
|
||||||
|
pat.patGuardExpr = null as *Expr;
|
||||||
return pat;
|
return pat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -904,6 +918,24 @@ func parserParsePattern(p: *Parser) -> *Pattern {
|
|||||||
pat.patRangeInclusive = inclusive;
|
pat.patRangeInclusive = inclusive;
|
||||||
pat.patChild1 = left;
|
pat.patChild1 = left;
|
||||||
pat.patChild2 = right;
|
pat.patChild2 = right;
|
||||||
|
// Range can still take a guard: `1..10 if x % 2 == 0`
|
||||||
|
if parserCheck(p, tkIf) {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
let guard: *Expr = parserParseExpr(p);
|
||||||
|
let gpat: *Pattern = parserMakePattern(pkGuarded, line, col);
|
||||||
|
gpat.patChild1 = pat;
|
||||||
|
gpat.patGuardExpr = guard;
|
||||||
|
return gpat;
|
||||||
|
}
|
||||||
|
return pat;
|
||||||
|
}
|
||||||
|
// Guarded pattern: `p if cond` (bindings from p visible in cond)
|
||||||
|
if parserCheck(p, tkIf) {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
let guard: *Expr = parserParseExpr(p);
|
||||||
|
let pat: *Pattern = parserMakePattern(pkGuarded, line, col);
|
||||||
|
pat.patChild1 = left;
|
||||||
|
pat.patGuardExpr = guard;
|
||||||
return pat;
|
return pat;
|
||||||
}
|
}
|
||||||
return left;
|
return left;
|
||||||
|
|||||||
+252
-23
@@ -388,6 +388,11 @@ func Sema_AddCapture(closureExpr: *Expr, name: String, typeKind: int) {
|
|||||||
// Enum payloads: Option::Some(value) → value:int (from variant field type).
|
// Enum payloads: Option::Some(value) → value:int (from variant field type).
|
||||||
func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
|
func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
|
||||||
if pat == null as *Pattern { return; }
|
if pat == null as *Pattern { return; }
|
||||||
|
// Guarded: bind from inner pattern only (`p if cond`)
|
||||||
|
if pat.kind == pkGuarded {
|
||||||
|
Sema_BindPattern(sema, pat.patChild1, subject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if pat.kind == pkIdent {
|
if pat.kind == pkIdent {
|
||||||
var sym: Symbol;
|
var sym: Symbol;
|
||||||
Sema_ZeroInitSymbol(&sym);
|
Sema_ZeroInitSymbol(&sym);
|
||||||
@@ -807,8 +812,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
|||||||
ai = ai + 1;
|
ai = ai + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Trait bounds checking for explicit generic calls: Max<Circle>(...)
|
// Trait bounds + inference for generic calls: Max / Iter_Map / Array_Push
|
||||||
// Must happen before indirect/direct call returns
|
|
||||||
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 {
|
if sym.kind == skFunc && sym.decl != null as *Decl {
|
||||||
@@ -831,13 +835,21 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
|||||||
return tyVoid;
|
return tyVoid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Direct call to named function
|
// Direct call to named function — substitute return type with inferred args
|
||||||
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 {
|
if sym.kind == skFunc && sym.decl != null as *Decl {
|
||||||
if sym.decl.retType != null as *TypeExpr {
|
if sym.decl.retType != null as *TypeExpr {
|
||||||
expr.refType = sym.decl.retType;
|
var retTe: *TypeExpr = sym.decl.retType;
|
||||||
return Sema_ResolveType(sema, sym.decl.retType);
|
// Substitute type params in return type when we inferred args
|
||||||
|
if expr.child1.genericTypeArgCount > 0 {
|
||||||
|
retTe = Sema_SubstTypeExpr(sym.decl.retType,
|
||||||
|
sym.decl.typeParam0, expr.child1.genericTypeArg0,
|
||||||
|
sym.decl.typeParam1, expr.child1.genericTypeArg1,
|
||||||
|
expr.child1.genericTypeArgCount);
|
||||||
|
}
|
||||||
|
expr.refType = retTe;
|
||||||
|
return Sema_ResolveType(sema, retTe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -851,6 +863,10 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
|||||||
|
|
||||||
// Cast — return target type
|
// Cast — return target type
|
||||||
if kind == ekCast {
|
if kind == ekCast {
|
||||||
|
// Must type-check the operand (enables generic inference on nested calls)
|
||||||
|
if expr.child1 != null as *Expr {
|
||||||
|
discard Sema_CheckExpr(sema, expr.child1);
|
||||||
|
}
|
||||||
if expr.refType != null as *TypeExpr {
|
if expr.refType != null as *TypeExpr {
|
||||||
return Sema_ResolveType(sema, expr.refType);
|
return Sema_ResolveType(sema, expr.refType);
|
||||||
}
|
}
|
||||||
@@ -990,6 +1006,16 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
|||||||
let savedScope: *Scope = sema.scope;
|
let savedScope: *Scope = sema.scope;
|
||||||
sema.scope = &armScope;
|
sema.scope = &armScope;
|
||||||
Sema_BindPattern(sema, arm.pattern, expr.child1);
|
Sema_BindPattern(sema, arm.pattern, expr.child1);
|
||||||
|
// Type-check `p if guard` (must be bool; sees pattern bindings)
|
||||||
|
if arm.pattern != null as *Pattern && arm.pattern.kind == pkGuarded {
|
||||||
|
if arm.pattern.patGuardExpr != null as *Expr {
|
||||||
|
let gt: int = Sema_CheckExpr(sema, arm.pattern.patGuardExpr);
|
||||||
|
if gt != tyBool && gt != tyUnknown {
|
||||||
|
Sema_EmitError(sema, arm.pattern.line, arm.pattern.column,
|
||||||
|
"match guard condition must be bool");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
let bt: int = Sema_CheckExpr(sema, arm.body);
|
let bt: int = Sema_CheckExpr(sema, arm.body);
|
||||||
sema.scope = savedScope;
|
sema.scope = savedScope;
|
||||||
if first {
|
if first {
|
||||||
@@ -1677,19 +1703,213 @@ func Sema_ExtractElemType(te: *TypeExpr) -> String {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infer generic type argument from call arguments.
|
// Substitute type params in a TypeExpr (shallow clone). Used for call return types
|
||||||
// Supports Array_* and Iter_* stdlib functions.
|
// after inference: Array<U> + U=String → Array with typeArgName0=String / Array_String.
|
||||||
|
func Sema_SubstTypeExpr(te: *TypeExpr, p0: String, a0: String, p1: String, a1: String, argc: int) -> *TypeExpr {
|
||||||
|
if te == null as *TypeExpr { return null as *TypeExpr; }
|
||||||
|
let r: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||||
|
r.kind = te.kind;
|
||||||
|
r.line = te.line;
|
||||||
|
r.column = te.column;
|
||||||
|
r.typeName = te.typeName;
|
||||||
|
r.pathStr = te.pathStr;
|
||||||
|
r.pathCount = te.pathCount;
|
||||||
|
r.typeArgName0 = te.typeArgName0;
|
||||||
|
r.typeArgName1 = te.typeArgName1;
|
||||||
|
r.typeArgCount = te.typeArgCount;
|
||||||
|
r.sliceElement = te.sliceElement;
|
||||||
|
r.pointerPointee = te.pointerPointee;
|
||||||
|
r.funcParams = te.funcParams;
|
||||||
|
r.funcRet = te.funcRet;
|
||||||
|
r.funcParamCount = te.funcParamCount;
|
||||||
|
r.tupleElems = te.tupleElems;
|
||||||
|
r.tupleCount = te.tupleCount;
|
||||||
|
|
||||||
|
if te.kind == tekNamed {
|
||||||
|
// Bare type param → concrete named type
|
||||||
|
if te.typeArgCount == 0 {
|
||||||
|
if argc >= 1 && String_Eq(te.typeName, p0) {
|
||||||
|
r.typeName = a0;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
if argc >= 2 && String_Eq(te.typeName, p1) {
|
||||||
|
r.typeName = a1;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Named with type args: Array<U> → Array_String (mangled) for downstream mono
|
||||||
|
if te.typeArgCount > 0 {
|
||||||
|
var na0: String = te.typeArgName0;
|
||||||
|
var na1: String = te.typeArgName1;
|
||||||
|
if argc >= 1 && String_Eq(na0, p0) { na0 = a0; }
|
||||||
|
if argc >= 2 && String_Eq(na0, p1) { na0 = a1; }
|
||||||
|
if argc >= 1 && String_Eq(na1, p0) { na1 = a0; }
|
||||||
|
if argc >= 2 && String_Eq(na1, p1) { na1 = a1; }
|
||||||
|
r.typeArgName0 = na0;
|
||||||
|
r.typeArgName1 = na1;
|
||||||
|
// Mangle for monomorphized struct name (Array_int, Iter_String)
|
||||||
|
if te.typeArgCount == 1 && !String_Eq(na0, "") {
|
||||||
|
r.typeName = String_Concat(String_Concat(te.typeName, "_"), na0);
|
||||||
|
r.typeArgCount = 0;
|
||||||
|
r.typeArgName0 = "";
|
||||||
|
} else if te.typeArgCount >= 2 {
|
||||||
|
r.typeName = String_Concat(String_Concat(te.typeName, "_"),
|
||||||
|
String_Concat(na0, String_Concat("_", na1)));
|
||||||
|
r.typeArgCount = 0;
|
||||||
|
r.typeArgName0 = "";
|
||||||
|
r.typeArgName1 = "";
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if te.kind == tekPointer || te.kind == tekRef || te.kind == tekMutRef {
|
||||||
|
r.pointerPointee = Sema_SubstTypeExpr(te.pointerPointee, p0, a0, p1, a1, argc);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
if te.kind == tekFunc {
|
||||||
|
r.funcRet = Sema_SubstTypeExpr(te.funcRet, p0, a0, p1, a1, argc);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind a type param name on the call callee if not already set.
|
||||||
|
func Sema_BindInferredArg(expr: *Expr, funcDecl: *Decl, tpName: String, typeName: String) {
|
||||||
|
if String_Eq(tpName, "") || String_Eq(typeName, "") { return; }
|
||||||
|
if expr.child1 == null as *Expr { return; }
|
||||||
|
if funcDecl.typeParamCount >= 1 && String_Eq(tpName, funcDecl.typeParam0) {
|
||||||
|
if String_Eq(expr.child1.genericTypeArg0, "") {
|
||||||
|
expr.child1.genericTypeArg0 = typeName;
|
||||||
|
}
|
||||||
|
if expr.child1.genericTypeArgCount < 1 { expr.child1.genericTypeArgCount = 1; }
|
||||||
|
}
|
||||||
|
if funcDecl.typeParamCount >= 2 && String_Eq(tpName, funcDecl.typeParam1) {
|
||||||
|
if String_Eq(expr.child1.genericTypeArg1, "") {
|
||||||
|
expr.child1.genericTypeArg1 = typeName;
|
||||||
|
}
|
||||||
|
if expr.child1.genericTypeArgCount < 2 { expr.child1.genericTypeArgCount = 2; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve concrete type name for a value expression (for bare type-param params).
|
||||||
|
func Sema_ArgTypeName(argExpr: *Expr) -> String {
|
||||||
|
if argExpr == null as *Expr { return ""; }
|
||||||
|
var argType: *TypeExpr = argExpr.refType;
|
||||||
|
if argType == null as *TypeExpr && argExpr.kind == ekUnary && argExpr.intValue == tkAmp {
|
||||||
|
if argExpr.child1 != null as *Expr { argType = argExpr.child1.refType; }
|
||||||
|
}
|
||||||
|
if argType == null as *TypeExpr { return ""; }
|
||||||
|
if argType.kind == tekNamed { return argType.typeName; }
|
||||||
|
if argType.kind == tekPointer && argType.pointerPointee != null as *TypeExpr {
|
||||||
|
if argType.pointerPointee.kind == tekNamed {
|
||||||
|
return argType.pointerPointee.typeName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infer type args from a param TypeExpr pattern against a concrete arg TypeExpr.
|
||||||
|
func Sema_UnifyInfer(expr: *Expr, funcDecl: *Decl, pattern: *TypeExpr, concrete: *TypeExpr) {
|
||||||
|
if pattern == null as *TypeExpr || concrete == null as *TypeExpr { return; }
|
||||||
|
|
||||||
|
// Bare type param: T / Acc / U
|
||||||
|
if pattern.kind == tekNamed && pattern.typeArgCount == 0 {
|
||||||
|
if String_Eq(pattern.typeName, funcDecl.typeParam0) || String_Eq(pattern.typeName, funcDecl.typeParam1) {
|
||||||
|
var cn: String = "";
|
||||||
|
if concrete.kind == tekNamed { cn = concrete.typeName; }
|
||||||
|
Sema_BindInferredArg(expr, funcDecl, pattern.typeName, cn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// *T / &T — unwrap pointer/ref on both sides
|
||||||
|
if pattern.kind == tekPointer || pattern.kind == tekRef || pattern.kind == tekMutRef {
|
||||||
|
var conc: *TypeExpr = concrete;
|
||||||
|
if concrete.kind == tekPointer || concrete.kind == tekRef || concrete.kind == tekMutRef {
|
||||||
|
conc = concrete.pointerPointee;
|
||||||
|
}
|
||||||
|
Sema_UnifyInfer(expr, funcDecl, pattern.pointerPointee, conc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Named with type args: Iter<T>, Array<U>, Map<K,V>
|
||||||
|
if pattern.kind == tekNamed && pattern.typeArgCount > 0 {
|
||||||
|
// concrete may be Iter with typeArgName0, or mangled Iter_int
|
||||||
|
if concrete.kind == tekNamed {
|
||||||
|
if concrete.typeArgCount > 0 {
|
||||||
|
if pattern.typeArgCount >= 1 && !String_Eq(pattern.typeArgName0, "") {
|
||||||
|
let te0: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||||
|
te0.kind = tekNamed;
|
||||||
|
te0.typeName = pattern.typeArgName0;
|
||||||
|
te0.typeArgCount = 0;
|
||||||
|
let ce0: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||||
|
ce0.kind = tekNamed;
|
||||||
|
ce0.typeName = concrete.typeArgName0;
|
||||||
|
ce0.typeArgCount = 0;
|
||||||
|
Sema_UnifyInfer(expr, funcDecl, te0, ce0);
|
||||||
|
}
|
||||||
|
if pattern.typeArgCount >= 2 && !String_Eq(pattern.typeArgName1, "") {
|
||||||
|
let te1: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||||
|
te1.kind = tekNamed;
|
||||||
|
te1.typeName = pattern.typeArgName1;
|
||||||
|
te1.typeArgCount = 0;
|
||||||
|
let ce1: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||||
|
ce1.kind = tekNamed;
|
||||||
|
ce1.typeName = concrete.typeArgName1;
|
||||||
|
ce1.typeArgCount = 0;
|
||||||
|
Sema_UnifyInfer(expr, funcDecl, te1, ce1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Mangled Array_int / Iter_String
|
||||||
|
let elem: String = Sema_ExtractElemType(concrete);
|
||||||
|
if !String_Eq(elem, "") && pattern.typeArgCount >= 1 {
|
||||||
|
let te0: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||||
|
te0.kind = tekNamed;
|
||||||
|
te0.typeName = pattern.typeArgName0;
|
||||||
|
te0.typeArgCount = 0;
|
||||||
|
let ce0: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||||
|
ce0.kind = tekNamed;
|
||||||
|
ce0.typeName = elem;
|
||||||
|
ce0.typeArgCount = 0;
|
||||||
|
Sema_UnifyInfer(expr, funcDecl, te0, ce0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// func(T)->U vs concrete function type
|
||||||
|
if pattern.kind == tekFunc {
|
||||||
|
var conc: *TypeExpr = concrete;
|
||||||
|
// Named function used as value: build tekFunc from its decl if needed — refType may already be tekFunc
|
||||||
|
if conc.kind == tekFunc {
|
||||||
|
// Params
|
||||||
|
var pp: *TypeExprList = pattern.funcParams;
|
||||||
|
var cp: *TypeExprList = conc.funcParams;
|
||||||
|
while pp != null as *TypeExprList && cp != null as *TypeExprList {
|
||||||
|
Sema_UnifyInfer(expr, funcDecl, pp.te, cp.te);
|
||||||
|
pp = pp.next;
|
||||||
|
cp = cp.next;
|
||||||
|
}
|
||||||
|
if pattern.funcRet != null as *TypeExpr && conc.funcRet != null as *TypeExpr {
|
||||||
|
Sema_UnifyInfer(expr, funcDecl, pattern.funcRet, conc.funcRet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infer generic type arguments from call arguments (structural).
|
||||||
|
// Handles *Array<T>, *Iter<T>, func(T)->U, bare Acc, etc.
|
||||||
func Sema_InferGenericArgs(sema: *Sema, funcDecl: *Decl, expr: *Expr) {
|
func Sema_InferGenericArgs(sema: *Sema, funcDecl: *Decl, expr: *Expr) {
|
||||||
if expr.callArgs == null as *ExprList { return; }
|
if expr.callArgs == null as *ExprList { return; }
|
||||||
|
if expr.child1 == null as *Expr { return; }
|
||||||
var argList: *ExprList = expr.callArgs;
|
var argList: *ExprList = expr.callArgs;
|
||||||
var pi: int = 0;
|
var pi: int = 0;
|
||||||
while argList != null as *ExprList && pi < funcDecl.paramCount {
|
while argList != null as *ExprList && pi < funcDecl.paramCount {
|
||||||
let argExpr: *Expr = argList.expr;
|
let argExpr: *Expr = argList.expr;
|
||||||
if argExpr == null as *Expr { argList = argList.next; pi = pi + 1; continue; }
|
if argExpr == null as *Expr { argList = argList.next; pi = pi + 1; continue; }
|
||||||
var argType: *TypeExpr = argExpr.refType;
|
|
||||||
if argType == null as *TypeExpr && argExpr.kind == ekUnary && argExpr.intValue == tkAmp {
|
|
||||||
argType = argExpr.child1.refType;
|
|
||||||
}
|
|
||||||
var paramType: *TypeExpr = null as *TypeExpr;
|
var paramType: *TypeExpr = null as *TypeExpr;
|
||||||
if pi == 0 { paramType = funcDecl.param0.refParamType; }
|
if pi == 0 { paramType = funcDecl.param0.refParamType; }
|
||||||
else if pi == 1 { paramType = funcDecl.param1.refParamType; }
|
else if pi == 1 { paramType = funcDecl.param1.refParamType; }
|
||||||
@@ -1701,20 +1921,29 @@ func Sema_InferGenericArgs(sema: *Sema, funcDecl: *Decl, expr: *Expr) {
|
|||||||
else if pi == 7 { paramType = funcDecl.param7.refParamType; }
|
else if pi == 7 { paramType = funcDecl.param7.refParamType; }
|
||||||
else if pi == 8 { paramType = funcDecl.param8.refParamType; }
|
else if pi == 8 { paramType = funcDecl.param8.refParamType; }
|
||||||
|
|
||||||
if paramType != null as *TypeExpr && paramType.kind == tekNamed && argType != null as *TypeExpr {
|
var argType: *TypeExpr = argExpr.refType;
|
||||||
let inferred: String = Sema_ExtractElemType(argType);
|
// &x → use type of x, wrap as pointer if pattern expects pointer
|
||||||
var typeName: String = inferred;
|
if argType == null as *TypeExpr && argExpr.kind == ekUnary && argExpr.intValue == tkAmp {
|
||||||
if String_Eq(typeName, "") && argType.kind == tekNamed {
|
if argExpr.child1 != null as *Expr {
|
||||||
typeName = argType.typeName;
|
argType = argExpr.child1.refType;
|
||||||
}
|
}
|
||||||
if !String_Eq(typeName, "") {
|
|
||||||
if funcDecl.typeParamCount >= 1 && String_Eq(paramType.typeName, funcDecl.typeParam0) && String_Eq(expr.child1.genericTypeArg0, "") {
|
|
||||||
expr.child1.genericTypeArg0 = typeName;
|
|
||||||
if expr.child1.genericTypeArgCount < 1 { expr.child1.genericTypeArgCount = 1; }
|
|
||||||
}
|
}
|
||||||
if funcDecl.typeParamCount >= 2 && String_Eq(paramType.typeName, funcDecl.typeParam1) && String_Eq(expr.child1.genericTypeArg1, "") {
|
// Named function as value: synthesize tekFunc from its declaration
|
||||||
expr.child1.genericTypeArg1 = typeName;
|
if (argType == null as *TypeExpr || argType.kind != tekFunc) && argExpr.kind == ekIdent {
|
||||||
if expr.child1.genericTypeArgCount < 2 { expr.child1.genericTypeArgCount = 2; }
|
let fsym: Symbol = Scope_Lookup(sema.scope, argExpr.strValue);
|
||||||
|
if fsym.kind == skFunc && fsym.decl != null as *Decl {
|
||||||
|
argType = Sema_BuildFuncTypeExprFromDecl(fsym.decl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if paramType != null as *TypeExpr && argType != null as *TypeExpr {
|
||||||
|
Sema_UnifyInfer(expr, funcDecl, paramType, argType);
|
||||||
|
// If pattern is *T and arg is bare T (from &x we unwrapped), re-wrap
|
||||||
|
if (paramType.kind == tekPointer || paramType.kind == tekRef || paramType.kind == tekMutRef)
|
||||||
|
&& argExpr.kind == ekUnary && argExpr.intValue == tkAmp {
|
||||||
|
// already handled via unwrap of pattern against pointee type of variable
|
||||||
|
if argExpr.child1 != null as *Expr && argExpr.child1.refType != null as *TypeExpr {
|
||||||
|
Sema_UnifyInfer(expr, funcDecl, paramType.pointerPointee, argExpr.child1.refType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,6 +195,46 @@ func Main() -> int {
|
|||||||
""")
|
""")
|
||||||
check(not res.hasErrors)
|
check(not res.hasErrors)
|
||||||
|
|
||||||
|
test "@[Checked] rejects two let-bound &mut of same var":
|
||||||
|
let res = checkSource("""
|
||||||
|
@[Checked]
|
||||||
|
func Main() -> int {
|
||||||
|
var x: int = 1;
|
||||||
|
let a: &mut int = &x;
|
||||||
|
let b: &mut int = &x;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
check(res.hasErrors)
|
||||||
|
check(res.diagnostics[0].message.contains("already mutably borrowed"))
|
||||||
|
|
||||||
|
test "@[Checked] rejects assign while mutably borrowed":
|
||||||
|
let res = checkSource("""
|
||||||
|
@[Checked]
|
||||||
|
func Main() -> int {
|
||||||
|
var x: int = 1;
|
||||||
|
let a: &mut int = &x;
|
||||||
|
x = 2;
|
||||||
|
return *a;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
check(res.hasErrors)
|
||||||
|
check(res.diagnostics[0].message.contains("mutably borrowed"))
|
||||||
|
|
||||||
|
test "@[Checked] rejects shared borrow while mutably borrowed":
|
||||||
|
let res = checkSource("""
|
||||||
|
@[Checked]
|
||||||
|
func Main() -> int {
|
||||||
|
var x: int = 1;
|
||||||
|
let a: &mut int = &x;
|
||||||
|
let b: &int = &x;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
check(res.hasErrors)
|
||||||
|
check(res.diagnostics[0].message.contains("shared-borrow") or
|
||||||
|
res.diagnostics[0].message.contains("mutably borrowed"))
|
||||||
|
|
||||||
test "borrow & expr with shared ref":
|
test "borrow & expr with shared ref":
|
||||||
let res = checkSource("""
|
let res = checkSource("""
|
||||||
struct Point {
|
struct Point {
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[Package]
|
||||||
|
Name = "exclusive_mut_let"
|
||||||
|
Version = "0.1.0"
|
||||||
|
Type = "bin"
|
||||||
|
[Build]
|
||||||
|
Output = "Bin"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
error: type errors in project
|
||||||
|
error: cannot mutably borrow 'x': already mutably borrowed
|
||||||
|
--> FILE:5:23
|
||||||
|
|
|
||||||
|
5 | let b: &mut int = &x;
|
||||||
|
| ^
|
||||||
|
= help: only one active '&mut' borrow is allowed at a time; end the borrow before reuse
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
@[Checked]
|
||||||
|
func Main() -> int {
|
||||||
|
var x: int = 1;
|
||||||
|
let a: &mut int = &x;
|
||||||
|
let b: &mut int = &x;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[Package]
|
||||||
|
Name = "use_while_mut_borrow"
|
||||||
|
Version = "0.1.0"
|
||||||
|
Type = "bin"
|
||||||
|
[Build]
|
||||||
|
Output = "Bin"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
error: type errors in project
|
||||||
|
error: cannot assign to 'x' while it is mutably borrowed
|
||||||
|
--> FILE:5:5
|
||||||
|
|
|
||||||
|
5 | x = 2;
|
||||||
|
| ^
|
||||||
|
= help: only one active '&mut' borrow is allowed at a time; end the borrow before reuse
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
@[Checked]
|
||||||
|
func Main() -> int {
|
||||||
|
var x: int = 1;
|
||||||
|
let a: &mut int = &x;
|
||||||
|
x = 2;
|
||||||
|
return *a;
|
||||||
|
}
|
||||||
+370
-10
@@ -3,8 +3,12 @@
|
|||||||
#
|
#
|
||||||
# Usage: bux-lsp
|
# Usage: bux-lsp
|
||||||
# The editor spawns this binary and communicates via stdin/stdout.
|
# The editor spawns this binary and communicates via stdin/stdout.
|
||||||
|
#
|
||||||
|
# Hover uses real bootstrap sema types when possible (globals + stdlib);
|
||||||
|
# completion/outline still use a fast lightweight scan.
|
||||||
|
|
||||||
import std/[json, os, strutils, streams, tables, osproc]
|
import std/[json, os, strutils, streams, tables, osproc, sequtils]
|
||||||
|
import lexer, parser, ast, sema, types, scope, source_location
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# JSON-RPC Transport
|
# JSON-RPC Transport
|
||||||
@@ -83,18 +87,25 @@ type
|
|||||||
kind: string ## function | variable | struct | enum | …
|
kind: string ## function | variable | struct | enum | …
|
||||||
detail: string ## signature / type annotation
|
detail: string ## signature / type annotation
|
||||||
container: string ## optional parent (module / type)
|
container: string ## optional parent (module / type)
|
||||||
|
fromSema: bool ## detail came from real type checker
|
||||||
DocumentState = ref object
|
DocumentState = ref object
|
||||||
uri: string
|
uri: string
|
||||||
content: string
|
content: string
|
||||||
version: int
|
version: int
|
||||||
symbols: Table[string, SymbolInfo]
|
symbols: Table[string, SymbolInfo]
|
||||||
ordered: seq[string] ## declaration order for outline
|
ordered: seq[string] ## declaration order for outline
|
||||||
|
## Full-project type index for hover (includes stdlib after sema enrich)
|
||||||
|
typeIndex: Table[string, string] ## name → type / signature string
|
||||||
|
kindIndex: Table[string, string] ## name → kind label
|
||||||
|
|
||||||
var
|
var
|
||||||
documents = initTable[string, DocumentState]()
|
documents = initTable[string, DocumentState]()
|
||||||
rootPath = ""
|
rootPath = ""
|
||||||
rootUri = ""
|
rootUri = ""
|
||||||
workspaceSymbols = initTable[string, tuple[uri: string, info: SymbolInfo]]()
|
workspaceSymbols = initTable[string, tuple[uri: string, info: SymbolInfo]]()
|
||||||
|
cachedStdlibDir = ""
|
||||||
|
cachedStdlibDecls: seq[Decl] = @[]
|
||||||
|
stdlibLoaded = false
|
||||||
|
|
||||||
proc getDoc(uri: string): DocumentState =
|
proc getDoc(uri: string): DocumentState =
|
||||||
if not documents.hasKey(uri):
|
if not documents.hasKey(uri):
|
||||||
@@ -326,6 +337,313 @@ proc analyzeFile(path: string, content: string): DocumentState =
|
|||||||
if not matchedTypeKw:
|
if not matchedTypeKw:
|
||||||
inc i
|
inc i
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Real sema types for hover
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
proc typeExprToStr(te: TypeExpr): string =
|
||||||
|
if te == nil: return "?"
|
||||||
|
case te.kind
|
||||||
|
of tekNamed:
|
||||||
|
result = te.typeName
|
||||||
|
if te.typeArgs.len > 0:
|
||||||
|
result &= "<" & te.typeArgs.mapIt(typeExprToStr(it)).join(", ") & ">"
|
||||||
|
of tekPath:
|
||||||
|
result = te.pathSegments.join("::")
|
||||||
|
of tekPointer:
|
||||||
|
result = "*" & typeExprToStr(te.pointerPointee)
|
||||||
|
of tekOwn:
|
||||||
|
result = "own " & typeExprToStr(te.pointerPointee)
|
||||||
|
of tekRef:
|
||||||
|
result = "&" & typeExprToStr(te.pointerPointee)
|
||||||
|
of tekMutRef:
|
||||||
|
result = "&mut " & typeExprToStr(te.pointerPointee)
|
||||||
|
of tekSlice:
|
||||||
|
result = typeExprToStr(te.sliceElement) & "[]"
|
||||||
|
of tekTuple:
|
||||||
|
result = "(" & te.tupleElements.mapIt(typeExprToStr(it)).join(", ") & ")"
|
||||||
|
of tekFunc:
|
||||||
|
let ps = te.funcParams.mapIt(typeExprToStr(it)).join(", ")
|
||||||
|
let ret = if te.funcRet != nil: typeExprToStr(te.funcRet) else: "void"
|
||||||
|
result = "func(" & ps & ") -> " & ret
|
||||||
|
of tekSelf:
|
||||||
|
result = "self"
|
||||||
|
of tekDynRef:
|
||||||
|
result = "&dyn " & te.dynInterface
|
||||||
|
|
||||||
|
proc formatFuncDetail(name: string, decl: Decl): string =
|
||||||
|
if decl == nil or decl.kind != dkFunc:
|
||||||
|
return "func " & name
|
||||||
|
var parts: seq[string] = @[]
|
||||||
|
for p in decl.declFuncParams:
|
||||||
|
var s = p.name
|
||||||
|
if p.ptype != nil:
|
||||||
|
s &= ": " & typeExprToStr(p.ptype)
|
||||||
|
parts.add(s)
|
||||||
|
result = "func " & name & "(" & parts.join(", ") & ")"
|
||||||
|
if decl.declFuncTypeParams.len > 0:
|
||||||
|
let tps = decl.declFuncTypeParams.mapIt(it.name).join(", ")
|
||||||
|
result = "func " & name & "<" & tps & ">(" & parts.join(", ") & ")"
|
||||||
|
if decl.declFuncReturnType != nil:
|
||||||
|
result &= " -> " & typeExprToStr(decl.declFuncReturnType)
|
||||||
|
|
||||||
|
proc symbolKindFromSema(sk: SymbolKind): string =
|
||||||
|
case sk
|
||||||
|
of skFunc: "function"
|
||||||
|
of skVar: "variable"
|
||||||
|
of skConst: "constant"
|
||||||
|
of skType: "type"
|
||||||
|
of skModule: "module"
|
||||||
|
|
||||||
|
proc findStdlibDirLocal(root: string): string =
|
||||||
|
if root.len == 0: return ""
|
||||||
|
let candidates = @[
|
||||||
|
root / "lib",
|
||||||
|
root / ".." / "lib",
|
||||||
|
getAppDir() / "lib",
|
||||||
|
getAppDir() / ".." / "lib",
|
||||||
|
getCurrentDir() / "lib"
|
||||||
|
]
|
||||||
|
for c in candidates:
|
||||||
|
if dirExists(c):
|
||||||
|
return c.absolutePath
|
||||||
|
# Walk up from root looking for lib/
|
||||||
|
var cur = root.absolutePath
|
||||||
|
for _ in 0 .. 6:
|
||||||
|
let lib = cur / "lib"
|
||||||
|
if dirExists(lib): return lib
|
||||||
|
let parent = cur.parentDir
|
||||||
|
if parent == cur: break
|
||||||
|
cur = parent
|
||||||
|
return ""
|
||||||
|
|
||||||
|
proc loadStdlibDecls(stdlibDir: string): seq[Decl] =
|
||||||
|
result = @[]
|
||||||
|
if stdlibDir.len == 0 or not dirExists(stdlibDir):
|
||||||
|
return
|
||||||
|
for path in walkDirRec(stdlibDir):
|
||||||
|
if not path.endsWith(".bux"): continue
|
||||||
|
try:
|
||||||
|
let source = readFile(path)
|
||||||
|
let lexRes = tokenize(source, path)
|
||||||
|
if lexRes.hasErrors: continue
|
||||||
|
let parseRes = parse(lexRes.tokens, path)
|
||||||
|
if parseRes.diagnostics.len > 0: continue
|
||||||
|
for item in parseRes.module.items:
|
||||||
|
if item.kind == dkModule:
|
||||||
|
for sub in item.declModuleItems:
|
||||||
|
result.add(sub)
|
||||||
|
else:
|
||||||
|
result.add(item)
|
||||||
|
except:
|
||||||
|
discard
|
||||||
|
|
||||||
|
proc ensureStdlibCached() =
|
||||||
|
if stdlibLoaded: return
|
||||||
|
stdlibLoaded = true
|
||||||
|
cachedStdlibDir = findStdlibDirLocal(rootPath)
|
||||||
|
if cachedStdlibDir.len == 0:
|
||||||
|
# try from open document path later
|
||||||
|
return
|
||||||
|
cachedStdlibDecls = loadStdlibDecls(cachedStdlibDir)
|
||||||
|
|
||||||
|
proc enrichWithSema(doc: DocumentState) =
|
||||||
|
## Run real bootstrap sema (file + stdlib) and fill typeIndex + upgrade symbols.
|
||||||
|
if doc.content.len == 0: return
|
||||||
|
let path = uriToPath(doc.uri)
|
||||||
|
ensureStdlibCached()
|
||||||
|
if cachedStdlibDecls.len == 0 and rootPath.len == 0:
|
||||||
|
# Try stdlib relative to the file
|
||||||
|
let tryRoot = path.parentDir.parentDir # …/src/Main.bux → package
|
||||||
|
cachedStdlibDir = findStdlibDirLocal(tryRoot)
|
||||||
|
if cachedStdlibDir.len > 0:
|
||||||
|
cachedStdlibDecls = loadStdlibDecls(cachedStdlibDir)
|
||||||
|
|
||||||
|
try:
|
||||||
|
let lexRes = tokenize(doc.content, path)
|
||||||
|
if lexRes.hasErrors:
|
||||||
|
return
|
||||||
|
let parseRes = parse(lexRes.tokens, path)
|
||||||
|
# Build unified module: stdlib first, then this file
|
||||||
|
var unified = newModule("lsp")
|
||||||
|
for d in cachedStdlibDecls:
|
||||||
|
unified.items.add(d)
|
||||||
|
for d in parseRes.module.items:
|
||||||
|
if d.kind == dkModule:
|
||||||
|
for sub in d.declModuleItems:
|
||||||
|
unified.items.add(sub)
|
||||||
|
else:
|
||||||
|
unified.items.add(d)
|
||||||
|
|
||||||
|
let (semaRes, semaCtx) = analyzeFull(unified)
|
||||||
|
discard semaRes # diagnostics already published via buxc
|
||||||
|
|
||||||
|
doc.typeIndex = initTable[string, string]()
|
||||||
|
doc.kindIndex = initTable[string, string]()
|
||||||
|
|
||||||
|
# Index entire global scope for hover (includes stdlib)
|
||||||
|
if semaCtx.globalScope != nil:
|
||||||
|
for name, sym in semaCtx.globalScope.table.pairs:
|
||||||
|
if name.len == 0: continue
|
||||||
|
var detail = ""
|
||||||
|
var kind = symbolKindFromSema(sym.kind)
|
||||||
|
if sym.kind == skFunc and sym.decl != nil and sym.decl.kind == dkFunc:
|
||||||
|
detail = formatFuncDetail(name, sym.decl)
|
||||||
|
elif sym.typ != nil and not sym.typ.isUnknown:
|
||||||
|
detail = name & ": " & sym.typ.toString
|
||||||
|
if sym.kind == skFunc:
|
||||||
|
detail = sym.typ.toString # already "func(...) -> T"
|
||||||
|
if not detail.startsWith("func"):
|
||||||
|
detail = "func " & name & " — " & detail
|
||||||
|
else:
|
||||||
|
# inject name: func name(...)
|
||||||
|
detail = detail.replace("func(", "func " & name & "(")
|
||||||
|
elif sym.kind == skConst:
|
||||||
|
detail = "const " & name & ": " & sym.typ.toString
|
||||||
|
elif sym.kind == skType:
|
||||||
|
detail = "type " & name
|
||||||
|
kind = "type"
|
||||||
|
else:
|
||||||
|
detail = (if sym.isMutable: "var " else: "let ") & name & ": " & sym.typ.toString
|
||||||
|
elif sym.decl != nil:
|
||||||
|
case sym.decl.kind
|
||||||
|
of dkStruct:
|
||||||
|
detail = "struct " & name
|
||||||
|
kind = "struct"
|
||||||
|
of dkEnum:
|
||||||
|
detail = "enum " & name
|
||||||
|
kind = "enum"
|
||||||
|
of dkUnion:
|
||||||
|
detail = "union " & name
|
||||||
|
kind = "struct"
|
||||||
|
of dkInterface:
|
||||||
|
detail = "interface " & name
|
||||||
|
kind = "interface"
|
||||||
|
of dkTypeAlias:
|
||||||
|
detail = "type " & name
|
||||||
|
if sym.decl.declAliasType != nil:
|
||||||
|
detail &= " = " & typeExprToStr(sym.decl.declAliasType)
|
||||||
|
kind = "type"
|
||||||
|
of dkFunc:
|
||||||
|
detail = formatFuncDetail(name, sym.decl)
|
||||||
|
kind = "function"
|
||||||
|
else:
|
||||||
|
detail = name
|
||||||
|
else:
|
||||||
|
detail = name
|
||||||
|
|
||||||
|
doc.typeIndex[name] = detail
|
||||||
|
doc.kindIndex[name] = kind
|
||||||
|
|
||||||
|
# Upgrade file-local symbols (already found by lightweight scan)
|
||||||
|
if doc.symbols.hasKey(name):
|
||||||
|
var info = doc.symbols[name]
|
||||||
|
info.detail = detail
|
||||||
|
info.kind = kind
|
||||||
|
info.fromSema = true
|
||||||
|
doc.symbols[name] = info
|
||||||
|
|
||||||
|
# Prefer THIS file's declarations over stdlib when names collide
|
||||||
|
# (e.g. user `Max<T>` vs lib/Math `Max(int64,int64)`).
|
||||||
|
for d in parseRes.module.items:
|
||||||
|
proc indexDecl(dd: Decl) =
|
||||||
|
case dd.kind
|
||||||
|
of dkFunc:
|
||||||
|
let n = dd.declFuncName
|
||||||
|
if n.len == 0: return
|
||||||
|
let detail = formatFuncDetail(n, dd)
|
||||||
|
doc.typeIndex[n] = detail
|
||||||
|
doc.kindIndex[n] = "function"
|
||||||
|
if doc.symbols.hasKey(n):
|
||||||
|
var info = doc.symbols[n]
|
||||||
|
info.detail = detail
|
||||||
|
info.kind = "function"
|
||||||
|
info.fromSema = true
|
||||||
|
doc.symbols[n] = info
|
||||||
|
else:
|
||||||
|
let line = max(0, int(dd.loc.line) - 1)
|
||||||
|
let col = max(0, int(dd.loc.column) - 1)
|
||||||
|
doc.symbols[n] = SymbolInfo(
|
||||||
|
line: line, col: col, kind: "function", detail: detail,
|
||||||
|
container: "", fromSema: true)
|
||||||
|
if n notin doc.ordered: doc.ordered.add(n)
|
||||||
|
of dkStruct:
|
||||||
|
let n = dd.declStructName
|
||||||
|
doc.typeIndex[n] = "struct " & n
|
||||||
|
doc.kindIndex[n] = "struct"
|
||||||
|
of dkEnum:
|
||||||
|
let n = dd.declEnumName
|
||||||
|
doc.typeIndex[n] = "enum " & n
|
||||||
|
doc.kindIndex[n] = "enum"
|
||||||
|
of dkTypeAlias:
|
||||||
|
let n = dd.declAliasName
|
||||||
|
var detail = "type " & n
|
||||||
|
if dd.declAliasType != nil:
|
||||||
|
detail &= " = " & typeExprToStr(dd.declAliasType)
|
||||||
|
doc.typeIndex[n] = detail
|
||||||
|
doc.kindIndex[n] = "type"
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
if d.kind == dkModule:
|
||||||
|
for sub in d.declModuleItems:
|
||||||
|
indexDecl(sub)
|
||||||
|
else:
|
||||||
|
indexDecl(d)
|
||||||
|
|
||||||
|
# Walk this file's AST for local lets with explicit types (function bodies)
|
||||||
|
proc walkBlock(blk: Block, container: string) =
|
||||||
|
if blk == nil: return
|
||||||
|
for stmt in blk.stmts:
|
||||||
|
case stmt.kind
|
||||||
|
of skLet:
|
||||||
|
let n = stmt.stmtLetName
|
||||||
|
if n.len == 0: continue
|
||||||
|
var typStr = ""
|
||||||
|
if stmt.stmtLetType != nil:
|
||||||
|
typStr = typeExprToStr(stmt.stmtLetType)
|
||||||
|
let kw = if stmt.stmtLetMut: "var" else: "let"
|
||||||
|
let detail = if typStr.len > 0: kw & " " & n & ": " & typStr else: kw & " " & n
|
||||||
|
let loc = stmt.loc
|
||||||
|
let line = max(0, int(loc.line) - 1)
|
||||||
|
let col = max(0, int(loc.column) - 1)
|
||||||
|
# Prefer sema-enriched detail if name already global; else add local
|
||||||
|
if not doc.symbols.hasKey(n) or not doc.symbols[n].fromSema:
|
||||||
|
doc.symbols[n] = SymbolInfo(
|
||||||
|
line: line, col: col, kind: "variable", detail: detail,
|
||||||
|
container: container, fromSema: typStr.len > 0)
|
||||||
|
if n notin doc.ordered:
|
||||||
|
doc.ordered.add(n)
|
||||||
|
if typStr.len > 0:
|
||||||
|
doc.typeIndex[n] = detail
|
||||||
|
doc.kindIndex[n] = "variable"
|
||||||
|
of skExpr:
|
||||||
|
if stmt.stmtExpr != nil and stmt.stmtExpr.kind == ekBlock:
|
||||||
|
walkBlock(stmt.stmtExpr.exprBlock, container)
|
||||||
|
of skIf:
|
||||||
|
walkBlock(stmt.stmtIfThen, container)
|
||||||
|
walkBlock(stmt.stmtIfElse, container)
|
||||||
|
for br in stmt.stmtIfElseIfs:
|
||||||
|
walkBlock(br.blk, container)
|
||||||
|
of skWhile:
|
||||||
|
walkBlock(stmt.stmtWhileBody, container)
|
||||||
|
of skFor:
|
||||||
|
walkBlock(stmt.stmtForBody, container)
|
||||||
|
of skLoop:
|
||||||
|
walkBlock(stmt.stmtLoopBody, container)
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
for d in parseRes.module.items:
|
||||||
|
if d.kind == dkFunc and d.declFuncBody != nil:
|
||||||
|
walkBlock(d.declFuncBody, d.declFuncName)
|
||||||
|
elif d.kind == dkModule:
|
||||||
|
for sub in d.declModuleItems:
|
||||||
|
if sub.kind == dkFunc and sub.declFuncBody != nil:
|
||||||
|
walkBlock(sub.declFuncBody, sub.declFuncName)
|
||||||
|
|
||||||
|
except:
|
||||||
|
discard # sema failures must not crash the LSP
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Diagnostics — run `buxc check` when available and parse Rust-style errors
|
# Diagnostics — run `buxc check` when available and parse Rust-style errors
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -470,6 +788,8 @@ proc analyzeAndPublishDiagnostics(stream: FileStream, doc: DocumentState) =
|
|||||||
let updated = analyzeFile(path, doc.content)
|
let updated = analyzeFile(path, doc.content)
|
||||||
doc.symbols = updated.symbols
|
doc.symbols = updated.symbols
|
||||||
doc.ordered = updated.ordered
|
doc.ordered = updated.ordered
|
||||||
|
# Keep / refresh real types for hover (does not replace lightweight outline)
|
||||||
|
enrichWithSema(doc)
|
||||||
let diags = runBuxcDiagnostics(path, doc.content)
|
let diags = runBuxcDiagnostics(path, doc.content)
|
||||||
publishDiagnostics(stream, doc.uri, diags)
|
publishDiagnostics(stream, doc.uri, diags)
|
||||||
|
|
||||||
@@ -628,7 +948,7 @@ proc handleDefinition(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
proc handleHover(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
|
proc handleHover(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
|
||||||
## Hover with accurate range for the word under the cursor.
|
## Hover with accurate range; prefer real sema types when available.
|
||||||
let uri = paramsNode["textDocument"]["uri"].getStr()
|
let uri = paramsNode["textDocument"]["uri"].getStr()
|
||||||
let position = paramsNode["position"]
|
let position = paramsNode["position"]
|
||||||
let lineNum = position["line"].getInt()
|
let lineNum = position["line"].getInt()
|
||||||
@@ -640,6 +960,10 @@ proc handleHover(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
|
|||||||
return
|
return
|
||||||
|
|
||||||
ensureAnalyzed(doc)
|
ensureAnalyzed(doc)
|
||||||
|
# Lazy sema enrich on first hover if not yet run (e.g. only didChange so far)
|
||||||
|
if doc.typeIndex.len == 0 and doc.content.len > 0:
|
||||||
|
enrichWithSema(doc)
|
||||||
|
|
||||||
let lines = doc.content.split("\n")
|
let lines = doc.content.split("\n")
|
||||||
if lineNum >= lines.len:
|
if lineNum >= lines.len:
|
||||||
sendResponse(stream, id, newJNull())
|
sendResponse(stream, id, newJNull())
|
||||||
@@ -656,19 +980,41 @@ proc handleHover(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
|
|||||||
return
|
return
|
||||||
let word = l[start ..< endC]
|
let word = l[start ..< endC]
|
||||||
|
|
||||||
var info: SymbolInfo
|
var detail = ""
|
||||||
|
var kind = ""
|
||||||
var found = false
|
var found = false
|
||||||
|
|
||||||
|
# Prefer file-local symbol (may be sema-upgraded)
|
||||||
if doc.symbols.hasKey(word):
|
if doc.symbols.hasKey(word):
|
||||||
info = doc.symbols[word]
|
let info = doc.symbols[word]
|
||||||
|
detail = info.detail
|
||||||
|
kind = info.kind
|
||||||
|
found = true
|
||||||
|
# Prefer pure sema typeIndex when richer
|
||||||
|
if doc.typeIndex.hasKey(word) and doc.typeIndex[word].len >= detail.len:
|
||||||
|
detail = doc.typeIndex[word]
|
||||||
|
if doc.kindIndex.hasKey(word):
|
||||||
|
kind = doc.kindIndex[word]
|
||||||
|
elif doc.typeIndex.hasKey(word):
|
||||||
|
detail = doc.typeIndex[word]
|
||||||
|
kind = if doc.kindIndex.hasKey(word): doc.kindIndex[word] else: "symbol"
|
||||||
found = true
|
found = true
|
||||||
elif workspaceSymbols.hasKey(word):
|
elif workspaceSymbols.hasKey(word):
|
||||||
info = workspaceSymbols[word].info
|
let info = workspaceSymbols[word].info
|
||||||
|
detail = info.detail
|
||||||
|
kind = info.kind
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
sendResponse(stream, id, newJNull())
|
sendResponse(stream, id, newJNull())
|
||||||
return
|
return
|
||||||
|
|
||||||
let md = "```bux\n" & info.detail & "\n```\n\n_" & info.kind & "_"
|
var md = "```bux\n" & detail & "\n```\n\n_" & kind & "_"
|
||||||
|
if doc.symbols.hasKey(word) and doc.symbols[word].fromSema:
|
||||||
|
md &= " · sema"
|
||||||
|
elif doc.typeIndex.hasKey(word):
|
||||||
|
md &= " · sema"
|
||||||
|
|
||||||
sendResponse(stream, id, %*{
|
sendResponse(stream, id, %*{
|
||||||
"contents": {"kind": "markdown", "value": md},
|
"contents": {"kind": "markdown", "value": md},
|
||||||
"range": {
|
"range": {
|
||||||
@@ -742,7 +1088,7 @@ proc handleMessage(stream: FileStream, msg: JsonNode) =
|
|||||||
"hoverProvider": true,
|
"hoverProvider": true,
|
||||||
"documentSymbolProvider": true
|
"documentSymbolProvider": true
|
||||||
},
|
},
|
||||||
"serverInfo": {"name": "bux-lsp", "version": "0.2.0"}
|
"serverInfo": {"name": "bux-lsp", "version": "0.3.0"}
|
||||||
})
|
})
|
||||||
if paramsNode.hasKey("rootPath") and paramsNode["rootPath"].kind != JNull:
|
if paramsNode.hasKey("rootPath") and paramsNode["rootPath"].kind != JNull:
|
||||||
rootPath = paramsNode["rootPath"].getStr()
|
rootPath = paramsNode["rootPath"].getStr()
|
||||||
@@ -750,10 +1096,14 @@ proc handleMessage(stream: FileStream, msg: JsonNode) =
|
|||||||
rootUri = paramsNode["rootUri"].getStr()
|
rootUri = paramsNode["rootUri"].getStr()
|
||||||
if rootPath.len == 0:
|
if rootPath.len == 0:
|
||||||
rootPath = uriToPath(rootUri)
|
rootPath = uriToPath(rootUri)
|
||||||
|
# Preload stdlib for hover types
|
||||||
|
if rootPath.len > 0:
|
||||||
|
ensureStdlibCached()
|
||||||
|
|
||||||
of "initialized":
|
of "initialized":
|
||||||
if rootPath.len > 0:
|
if rootPath.len > 0:
|
||||||
scanWorkspace(rootPath)
|
scanWorkspace(rootPath)
|
||||||
|
ensureStdlibCached()
|
||||||
|
|
||||||
of "shutdown":
|
of "shutdown":
|
||||||
sendResponse(stream, id, %*{})
|
sendResponse(stream, id, %*{})
|
||||||
@@ -769,6 +1119,7 @@ proc handleMessage(stream: FileStream, msg: JsonNode) =
|
|||||||
doc.content = content
|
doc.content = content
|
||||||
if td.hasKey("version"):
|
if td.hasKey("version"):
|
||||||
doc.version = td["version"].getInt()
|
doc.version = td["version"].getInt()
|
||||||
|
# Lightweight scan + sema enrich + buxc diagnostics
|
||||||
analyzeAndPublishDiagnostics(stream, doc)
|
analyzeAndPublishDiagnostics(stream, doc)
|
||||||
|
|
||||||
of "textDocument/didChange":
|
of "textDocument/didChange":
|
||||||
@@ -780,16 +1131,25 @@ proc handleMessage(stream: FileStream, msg: JsonNode) =
|
|||||||
doc.content = changes[changes.len - 1]["text"].getStr()
|
doc.content = changes[changes.len - 1]["text"].getStr()
|
||||||
if td.hasKey("version"):
|
if td.hasKey("version"):
|
||||||
doc.version = td["version"].getInt()
|
doc.version = td["version"].getInt()
|
||||||
# Refresh symbols immediately (no buxc — diagnostics on save)
|
# Fast path: lightweight symbols only; keep previous typeIndex until save/hover refresh
|
||||||
let updated = analyzeFile(uriToPath(uri), doc.content)
|
let updated = analyzeFile(uriToPath(uri), doc.content)
|
||||||
doc.symbols = updated.symbols
|
doc.symbols = updated.symbols
|
||||||
doc.ordered = updated.ordered
|
doc.ordered = updated.ordered
|
||||||
|
# Re-apply typeIndex details onto matching names (don't drop sema types mid-edit)
|
||||||
|
for name, detail in doc.typeIndex.pairs:
|
||||||
|
if doc.symbols.hasKey(name):
|
||||||
|
var info = doc.symbols[name]
|
||||||
|
info.detail = detail
|
||||||
|
info.fromSema = true
|
||||||
|
if doc.kindIndex.hasKey(name):
|
||||||
|
info.kind = doc.kindIndex[name]
|
||||||
|
doc.symbols[name] = info
|
||||||
|
|
||||||
of "textDocument/didSave":
|
of "textDocument/didSave":
|
||||||
let td = paramsNode["textDocument"]
|
let td = paramsNode["textDocument"]
|
||||||
let uri = td["uri"].getStr()
|
let uri = td["uri"].getStr()
|
||||||
let doc = getDoc(uri)
|
discard getDoc(uri)
|
||||||
analyzeAndPublishDiagnostics(stream, doc)
|
analyzeAndPublishDiagnostics(stream, getDoc(uri))
|
||||||
|
|
||||||
of "textDocument/completion":
|
of "textDocument/completion":
|
||||||
handleCompletion(stream, id, paramsNode)
|
handleCompletion(stream, id, paramsNode)
|
||||||
|
|||||||
Reference in New Issue
Block a user