feat: Phase 8.2, 8.4, 8.5, 9.1 + C backend fixes

Phase 8.2 — Gradual Ownership:
- Add tkRef/tkMutRef types and `mut` keyword
- Add @[Checked] attribute for opt-in borrow checking
- Reject assignment through &T in checked functions
- examples/ownership.bux

Phase 8.4 — CTFE:
- Evaluate const func at compile-time via evalExpr/evalBlock
- Fold const declarations to literals; emit #define in C
- examples/ctfe.bux (Factorial(10) → 3628800)

Phase 8.5 — Trait Bounds:
- Change declFuncTypeParams from seq[string] to seq[TypeParam] (name + bound)
- Parser handles <T: Comparable>
- Sema checks typeImplements at call sites
- Fix C backend: generic receivers + pointer self field access
- examples/trait_bounds.bux

Phase 9.1 — Package Manager:
- Inline tables/arrays in TOML parser
- bux add, bux install, bux.lock generation
- Dependency resolution with git/path sources
- Build pipeline merges dependency .bux sources

C Backend Fixes:
- resolveExprType(ekIdent) now applies typeSubst for generic params
- Method desugaring works for monomorphized generic receivers
- Pointer checks use isPointer (covers tkRef/tkMutRef)
- Field access on &T emits -> instead of .

Remove accidentally committed test binaries from tracking
This commit is contained in:
2026-05-31 23:48:45 +03:00
parent b1f1fc277c
commit 8e255b2125
21 changed files with 1360 additions and 111 deletions
+10 -3
View File
@@ -272,6 +272,13 @@ type
of skDecl:
stmtDecl*: Decl
# ---------------------------------------------------------------------------
# Type Parameters (for generics with trait bounds)
# ---------------------------------------------------------------------------
TypeParam* = object
name*: string
bounds*: seq[string] ## e.g. ["Comparable"] for <T: Comparable>
# ---------------------------------------------------------------------------
# Declarations
# ---------------------------------------------------------------------------
@@ -325,13 +332,13 @@ type
declFuncCallConv*: CallingConvention
declFuncConst*: bool ## const func — evaluable at compile time
declFuncName*: string
declFuncTypeParams*: seq[string]
declFuncTypeParams*: seq[TypeParam]
declFuncParams*: seq[Param]
declFuncReturnType*: TypeExpr ## nil if void/inferred
declFuncBody*: Block ## nil for signature-only
of dkStruct:
declStructName*: string
declStructTypeParams*: seq[string]
declStructTypeParams*: seq[TypeParam]
declStructFields*: seq[StructField]
of dkEnum:
declEnumName*: string
@@ -345,7 +352,7 @@ type
declInterfaceMethods*: seq[Decl] ## FuncDecl signatures only
of dkImpl:
declImplTypeName*: string
declImplTypeParams*: seq[string] ## type parameters for generic impl: extend Box<T>
declImplTypeParams*: seq[TypeParam] ## type parameters for generic impl: extend Box<T>
declImplInterface*: string ## empty if not for interface
declImplMethods*: seq[Decl]
of dkModule:
+1 -1
View File
@@ -56,7 +56,7 @@ proc typeToC*(typ: Type): string =
of tkUInt: return "unsigned int"
of tkFloat32: return "float"
of tkFloat64: return "double"
of tkPointer:
of tkPointer, tkRef, tkMutRef:
if typ.inner.len > 0:
return typeToC(typ.inner[0]) & "*"
return "void*"
+156 -3
View File
@@ -20,6 +20,8 @@ Usage: bux [options] <command> [command-options]
Commands:
new <name> Create a new Bux package
init Initialize a Bux package in the current directory
add <name> [ver] Add a dependency (--path, --git)
install Resolve and install dependencies
build Build the current package
run Build and run the current package
check Type-check the current package
@@ -141,9 +143,118 @@ Output = "Bin"
printInfo(&"Initialized Bux package '{name}'", useColor)
return 0
proc cmdAdd*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = getCurrentDir()
let manifestPath = root / "bux.toml"
if not fileExists(manifestPath):
printError("no bux.toml found", useColor)
return 1
if args.len == 0:
printError("usage: bux add <name> [version] [--path <path>] [--git <url>]", useColor)
return 1
let depName = args[0]
var version = "*"
var path = ""
var gitUrl = ""
var i = 1
while i < args.len:
case args[i]
of "--path":
if i + 1 < args.len:
path = args[i + 1]
inc i
else:
printError("--path requires a value", useColor)
return 1
of "--git":
if i + 1 < args.len:
gitUrl = args[i + 1]
inc i
else:
printError("--git requires a value", useColor)
return 1
else:
version = args[i]
inc i
# Append to bux.toml
var depLine = ""
if path.len > 0:
depLine = &"{depName} = {{ Path = \"{path}\" }}"
elif gitUrl.len > 0:
depLine = &"{depName} = {{ Version = \"{version}\", Source = \"{gitUrl}\" }}"
else:
depLine = &"{depName} = \"{version}\""
var content = readFile(manifestPath)
# Ensure [Dependencies] section exists
if content.find("[Dependencies]") < 0:
content.add("\n[Dependencies]\n")
# Append dependency line
content.add(depLine & "\n")
writeFile(manifestPath, content)
if not opts.quiet:
printInfo(&"Added dependency '{depName}' to bux.toml", useColor)
return 0
proc cmdInstall*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = getCurrentDir()
let manifestPath = root / "bux.toml"
if not fileExists(manifestPath):
printError("no bux.toml found", useColor)
return 1
let man = loadManifest(manifestPath)
var lock = Lockfile(entries: @[])
let cacheDir = getHomeDir() / ".bux" / "packages"
if not dirExists(cacheDir):
createDir(cacheDir)
# Resolve each dependency
for dep in man.dependencies:
case dep.kind
of dkPath:
let absPath = if dep.path.isAbsolute: dep.path else: root / dep.path
if not dirExists(absPath):
printError(&"path dependency not found: {absPath}", useColor)
return 1
# Read dependency manifest
let depManifestPath = absPath / "bux.toml"
if fileExists(depManifestPath):
let depMan = loadManifest(depManifestPath)
lock.entries.add(LockEntry(name: dep.name, version: depMan.version, source: absPath))
else:
lock.entries.add(LockEntry(name: dep.name, version: "0.0.0", source: absPath))
if not opts.quiet:
printInfo(&"Resolved path dependency '{dep.name}' from {absPath}", useColor)
of dkGit:
let depDir = cacheDir / dep.name
if not dirExists(depDir):
if not opts.quiet:
printInfo(&"Cloning '{dep.name}' from {dep.gitUrl}...", useColor)
let (outp, code) = execCmdEx(&"git clone {dep.gitUrl} {depDir} 2>&1")
if code != 0:
printError(&"failed to clone {dep.gitUrl}: {outp}", useColor)
return 1
else:
if not opts.quiet:
printInfo(&"Using cached '{dep.name}' from {depDir}", useColor)
lock.entries.add(LockEntry(name: dep.name, version: dep.gitVersion, source: dep.gitUrl))
of dkVersion:
# For version-based deps without a registry, we just record them
# TODO: lookup in registry
lock.entries.add(LockEntry(name: dep.name, version: dep.versionReq, source: "registry"))
if not opts.quiet:
printInfo(&"Recorded dependency '{dep.name}' = {dep.versionReq}", useColor)
# Save lockfile
let lockPath = root / "bux.lock"
saveLockfile(lockPath, lock)
if not opts.quiet:
printInfo(&"Generated {lockPath}", useColor)
return 0
proc collectStdlibDecls(stdlibDir: string): seq[Decl]
proc getDeclName(d: Decl): string
proc mergeDecls(stdlibDecls: seq[Decl], userDecls: seq[Decl]): seq[Decl]
proc collectDepDecls(lock: Lockfile, root: string, opts: GlobalOptions): seq[Decl]
proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
@@ -205,7 +316,10 @@ proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
stdlibDir = path
break
let stdlibDecls = collectStdlibDecls(stdlibDir)
let mergedItems = mergeDecls(stdlibDecls, allModuleItems)
let lock = loadLockfile(root / "bux.lock")
let depDecls = collectDepDecls(lock, root, opts)
let stdlibAndDeps = mergeDecls(stdlibDecls, depDecls)
let mergedItems = mergeDecls(stdlibAndDeps, allModuleItems)
var unifiedModule = newModule("main")
unifiedModule.items = mergedItems
@@ -251,6 +365,38 @@ proc getDeclName(d: Decl): string =
of dkTypeAlias: d.declAliasName
else: ""
proc collectDepDecls(lock: Lockfile, root: string, opts: GlobalOptions): seq[Decl] =
## Collect declarations from all locked dependencies.
let cacheDir = getHomeDir() / ".bux" / "packages"
let useColor = shouldUseColor(opts)
for entry in lock.entries:
var depSrcDir = ""
if dirExists(entry.source):
# Path-based dependency
depSrcDir = entry.source / "src"
elif entry.source.startsWith("http") or entry.source.startsWith("git@"):
# Git-based dependency in cache
depSrcDir = cacheDir / entry.name / "src"
if depSrcDir == "" or not dirExists(depSrcDir):
continue
for kind, path in walkDir(depSrcDir):
if kind == pcFile and path.endsWith(".bux"):
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 decl in parseRes.module.items:
if decl.kind == dkModule:
for sub in decl.declModuleItems:
result.add(sub)
else:
result.add(decl)
if not opts.quiet:
printInfo(&"Loaded dependency '{entry.name}' from {depSrcDir}", useColor)
proc mergeDecls(stdlibDecls: seq[Decl], userDecls: seq[Decl]): seq[Decl] =
## Merge stdlib and user declarations.
## User funcs shadow stdlib funcs with the same name (simple overload avoidance).
@@ -301,6 +447,10 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
# Collect stdlib declarations once
let stdlibDecls = collectStdlibDecls(stdlibDir)
# Collect dependency declarations from lockfile
let lock = loadLockfile(root / "bux.lock")
let depDecls = collectDepDecls(lock, root, opts)
# Phase 1: Parse all .bux files and collect declarations
var allModuleItems: seq[Decl] = @[]
var foundMain = false
@@ -335,8 +485,9 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
printError("no Main.bux found in src/", useColor)
return 1
# Phase 2: Merge all project declarations with stdlib
let mergedItems = mergeDecls(stdlibDecls, allModuleItems)
# Phase 2: Merge stdlib + deps + project (later shadow earlier)
let stdlibAndDeps = mergeDecls(stdlibDecls, depDecls)
let mergedItems = mergeDecls(stdlibAndDeps, allModuleItems)
# Create unified module
var unifiedModule = newModule("main")
@@ -441,6 +592,8 @@ proc runCli*(args: seq[string]): int =
case cmd
of "new": return cmdNew(cmdArgs, opts)
of "init": return cmdInit(cmdArgs, opts)
of "add": return cmdAdd(cmdArgs, opts)
of "install": return cmdInstall(cmdArgs, opts)
of "build": return cmdBuild(cmdArgs, opts)
of "run": return cmdRun(cmdArgs, opts)
of "check": return cmdCheck(cmdArgs, opts)
+24 -28
View File
@@ -166,6 +166,10 @@ proc substituteType(ctx: var LowerCtx, te: TypeExpr, subst: Table[string, Type])
return ctx.resolveTypeExpr(te)
of tekPointer:
return makePointer(substituteType(ctx, te.pointerPointee, subst))
of tekRef:
return makeRef(substituteType(ctx, te.pointerPointee, subst))
of tekMutRef:
return makeMutRef(substituteType(ctx, te.pointerPointee, subst))
of tekSlice:
return makeSlice(substituteType(ctx, te.sliceElement, subst))
of tekTuple:
@@ -194,7 +198,7 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
var concreteArgs: seq[Type] = @[]
for j, tp in genericDecl.declStructTypeParams:
if j < te.typeArgs.len:
subst[tp] = ctx.resolveTypeExpr(te.typeArgs[j])
subst[tp.name] = ctx.resolveTypeExpr(te.typeArgs[j])
for arg in te.typeArgs:
concreteArgs.add(ctx.resolveTypeExpr(arg))
for f in genericDecl.declStructFields:
@@ -257,7 +261,7 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
if sym != nil and sym.typ != nil: return sym.typ
# Check local variables and parameters tracked in varTypeExprs
if ctx.varTypeExprs.hasKey(expr.exprIdent):
return ctx.resolveTypeExpr(ctx.varTypeExprs[expr.exprIdent])
return substituteType(ctx, ctx.varTypeExprs[expr.exprIdent], ctx.typeSubst)
# Check current function parameters (fallback for untracked params)
if ctx.currentFuncDecl != nil:
var params: seq[Param] = @[]
@@ -267,21 +271,7 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
else: discard
for p in params:
if p.name == expr.exprIdent and p.ptype != nil:
case p.ptype.kind
of tekNamed:
case p.ptype.typeName
of "int", "int32": return makeInt()
of "int64": return makeInt64()
of "float64": return makeFloat64()
of "float32": return makeFloat32()
of "bool": return makeBool()
of "uint": return makeUInt()
of "void": return makeVoid()
else: return makeNamed(p.ptype.typeName)
of tekPointer:
let pointeeType = ctx.resolveTypeExpr(p.ptype.pointerPointee)
return makePointer(pointeeType)
else: discard
return substituteType(ctx, p.ptype, ctx.typeSubst)
return makeUnknown()
of ekSelf:
# Look up self parameter type from current function
@@ -329,7 +319,7 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
of ekField:
var objType = ctx.resolveExprType(expr.exprFieldObj)
# Auto-dereference pointer types for field access
if objType.kind == tkPointer and objType.inner.len > 0:
if objType.isPointer and objType.inner.len > 0:
objType = objType.inner[0]
if objType.kind == tkNamed:
let sym = ctx.globalScope.lookup(objType.name)
@@ -460,7 +450,13 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
var receiverTypeName = ""
if receiverType.kind == tkNamed:
receiverTypeName = receiverType.name
elif receiverType.kind == tkPointer and receiverType.inner.len > 0 and receiverType.inner[0].kind == tkNamed:
if ctx.typeSubst.hasKey(receiverTypeName):
let substituted = ctx.typeSubst[receiverTypeName]
if substituted.kind == tkNamed:
receiverTypeName = substituted.name
elif substituted.isPointer and substituted.inner.len > 0 and substituted.inner[0].kind == tkNamed:
receiverTypeName = substituted.inner[0].name
elif receiverType.isPointer and receiverType.inner.len > 0 and receiverType.inner[0].kind == tkNamed:
receiverTypeName = receiverType.inner[0].name
# Look up method for receiver type specifically
@@ -477,7 +473,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
var args: seq[HirNode] = @[]
let loweredReceiver = ctx.lowerExpr(receiverExpr)
# Auto-address if method expects pointer but receiver is value
if minfo.params.len > 0 and minfo.params[0].kind == tkPointer and receiverType.kind != tkPointer:
if minfo.params.len > 0 and minfo.params[0].isPointer and not receiverType.isPointer:
args.add(hirUnary(tkAmp, loweredReceiver, makePointer(receiverType), loc))
else:
args.add(loweredReceiver)
@@ -558,7 +554,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
let objType = ctx.resolveExprType(expr.exprFieldObj)
let base = ctx.lowerExpr(expr.exprFieldObj)
# Auto-dereference pointer types for field access
if objType.kind == tkPointer:
if objType.isPointer:
let arrowPtr = HirNode(kind: hArrowField, arrowFieldBase: base,
arrowFieldName: expr.exprFieldName,
typ: makePointer(typ), loc: loc)
@@ -1010,7 +1006,7 @@ proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs:
if i > 0: typeSuffix.add("_")
if i < typeArgs.len:
let argType = ctx.resolveTypeExpr(typeArgs[i])
subst[tp] = argType
subst[tp.name] = argType
typeSuffix.add(argType.toString)
else:
typeSuffix.add("unknown")
@@ -1178,12 +1174,12 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
let targ = inst.typeArgs[j]
if targ.kind == tekNamed:
case targ.typeName
of "int", "int32": subst[tp] = makeInt()
of "int64": subst[tp] = makeInt64()
of "float64": subst[tp] = makeFloat64()
of "float32": subst[tp] = makeFloat32()
of "bool": subst[tp] = makeBool()
else: subst[tp] = makeNamed(targ.typeName)
of "int", "int32": subst[tp.name] = makeInt()
of "int64": subst[tp.name] = makeInt64()
of "float64": subst[tp.name] = makeFloat64()
of "float32": subst[tp.name] = makeFloat32()
of "bool": subst[tp.name] = makeBool()
else: subst[tp.name] = makeNamed(targ.typeName)
# Create specialized declaration
var specDecl = Decl(
+210 -15
View File
@@ -1,4 +1,4 @@
import std/[strutils, os, tables]
import std/[strutils, os, tables, sequtils, strformat]
type
PackageType* = enum
@@ -7,57 +7,245 @@ type
ptStaticLibrary
ptSource
DepKind* = enum
dkVersion ## "1.0" or "*"
dkPath ## { Path = "../Lib" }
dkGit ## { Version = "1.4", Source = "https://..." }
Dependency* = object
name*: string
case kind*: DepKind
of dkVersion:
versionReq*: string
of dkPath:
path*: string
of dkGit:
gitUrl*: string
gitVersion*: string
Manifest* = object
name*: string
version*: string
pkgType*: PackageType
output*: string ## from [Build] Output
dependencies*: OrderedTableRef[string, string]
output*: string
dependencies*: seq[Dependency]
devDependencies*: seq[Dependency]
buildDependencies*: seq[Dependency]
# ---------------------------------------------------------------------------
# Extended TOML parser (supports inline tables and arrays)
# ---------------------------------------------------------------------------
type
TomlValueKind = enum
tvkString, tvkTable
tvkString, tvkTable, tvkArray, tvkInlineTable
TomlValue = object
case kind*: TomlValueKind
of tvkString:
strVal*: string
of tvkTable:
tableVal*: OrderedTableRef[string, TomlValue]
of tvkArray:
arrayVal*: seq[TomlValue]
of tvkInlineTable:
inlineVal*: OrderedTableRef[string, TomlValue]
proc parseSimpleToml(path: string): OrderedTableRef[string, TomlValue] =
proc parseInlineTable(s: string): OrderedTableRef[string, TomlValue] =
result = newOrderedTable[string, TomlValue]()
var content = s.strip()
if content.len >= 2 and content[0] == '{' and content[^1] == '}':
content = content[1 ..< ^1].strip()
var i = 0
while i < content.len:
# skip whitespace
while i < content.len and content[i] in Whitespace:
inc i
if i >= content.len: break
# parse key
var keyStart = i
while i < content.len and content[i] notin {'=', ' ', '\t'}:
inc i
let key = content[keyStart ..< i].strip()
# skip to =
while i < content.len and content[i] in Whitespace:
inc i
if i >= content.len or content[i] != '=': break
inc i
while i < content.len and content[i] in Whitespace:
inc i
# parse value
var valStart = i
if i < content.len and content[i] == '"':
inc i
while i < content.len and content[i] != '"':
inc i
if i < content.len: inc i
let val = content[valStart + 1 ..< i - 1]
result[key] = TomlValue(kind: tvkString, strVal: val)
elif i < content.len and content[i] == '{':
# nested inline table (skip for now)
var braceCount = 1
inc i
while i < content.len and braceCount > 0:
if content[i] == '{': inc braceCount
elif content[i] == '}': dec braceCount
inc i
let val = content[valStart ..< i]
result[key] = TomlValue(kind: tvkInlineTable, inlineVal: newOrderedTable[string, TomlValue]())
else:
while i < content.len and content[i] notin {',', ' ', '\t'}:
inc i
let val = content[valStart ..< i].strip()
result[key] = TomlValue(kind: tvkString, strVal: val)
# skip to comma or end
while i < content.len and content[i] in Whitespace:
inc i
if i < content.len and content[i] == ',':
inc i
proc parseArray(s: string): seq[TomlValue] =
result = @[]
var content = s.strip()
if content.len >= 2 and content[0] == '[' and content[^1] == ']':
content = content[1 ..< ^1].strip()
if content.len == 0: return
for item in content.split(','):
let val = item.strip()
if val.len >= 2 and val[0] == '"' and val[^1] == '"':
result.add(TomlValue(kind: tvkString, strVal: val[1 ..< ^1]))
else:
result.add(TomlValue(kind: tvkString, strVal: val))
proc parseValue(valStr: string): TomlValue =
let val = valStr.strip()
if val.len >= 2 and val[0] == '"' and val[^1] == '"':
return TomlValue(kind: tvkString, strVal: val[1 ..< ^1])
elif val.len >= 2 and val[0] == '[' and val[^1] == ']':
return TomlValue(kind: tvkArray, arrayVal: parseArray(val))
elif val.len >= 2 and val[0] == '{' and val[^1] == '}':
return TomlValue(kind: tvkInlineTable, inlineVal: parseInlineTable(val))
else:
return TomlValue(kind: tvkString, strVal: val)
proc parseToml*(path: string): OrderedTableRef[string, TomlValue] =
result = newOrderedTable[string, TomlValue]()
if not fileExists(path):
return result
let content = readFile(path)
var currentTable = ""
var currentArrayTable = ""
for rawLine in content.splitLines():
let line = rawLine.strip()
if line.len == 0 or line.startsWith("#"):
continue
if line.startsWith("[") and line.endsWith("]"):
if line.startsWith("[[") and line.endsWith("]]"):
# Array of tables
currentArrayTable = line[2 ..< ^2]
currentTable = ""
let newTable = TomlValue(kind: tvkTable, tableVal: newOrderedTable[string, TomlValue]())
if not result.hasKey(currentArrayTable):
result[currentArrayTable] = TomlValue(kind: tvkArray, arrayVal: @[newTable])
else:
result[currentArrayTable].arrayVal.add(newTable)
elif line.startsWith("[") and line.endsWith("]"):
currentTable = line[1 ..< ^1]
currentArrayTable = ""
if not result.hasKey(currentTable):
result[currentTable] = TomlValue(kind: tvkTable, tableVal: newOrderedTable[string, TomlValue]())
else:
let eqIdx = line.find('=')
if eqIdx >= 0:
let key = line[0 ..< eqIdx].strip()
var val = line[eqIdx + 1 .. ^1].strip()
# Remove surrounding quotes
if val.len >= 2 and val[0] == '"' and val[^1] == '"':
val = val[1 ..< ^1]
if currentTable != "" and result.hasKey(currentTable):
result[currentTable].tableVal[key] = TomlValue(kind: tvkString, strVal: val)
let valStr = line[eqIdx + 1 .. ^1].strip()
let val = parseValue(valStr)
if currentArrayTable != "" and result.hasKey(currentArrayTable):
let arr = result[currentArrayTable].arrayVal
if arr.len > 0:
arr[^1].tableVal[key] = val
elif currentTable != "" and result.hasKey(currentTable):
result[currentTable].tableVal[key] = val
else:
result[key] = TomlValue(kind: tvkString, strVal: val)
result[key] = val
proc parseDepTable(table: OrderedTableRef[string, TomlValue]): seq[Dependency] =
result = @[]
for name, val in table:
case val.kind
of tvkString:
result.add(Dependency(name: name, kind: dkVersion, versionReq: val.strVal))
of tvkInlineTable:
let t = val.inlineVal
if t.hasKey("Path"):
result.add(Dependency(name: name, kind: dkPath, path: t["Path"].strVal))
elif t.hasKey("Source"):
var ver = "*"
if t.hasKey("Version"):
ver = t["Version"].strVal
result.add(Dependency(name: name, kind: dkGit, gitUrl: t["Source"].strVal, gitVersion: ver))
elif t.hasKey("Version"):
result.add(Dependency(name: name, kind: dkVersion, versionReq: t["Version"].strVal))
else:
result.add(Dependency(name: name, kind: dkVersion, versionReq: "*"))
else:
discard
# ---------------------------------------------------------------------------
# Lockfile
# ---------------------------------------------------------------------------
type
LockEntry* = object
name*: string
version*: string
source*: string
checksum*: string
Lockfile* = object
entries*: seq[LockEntry]
proc loadLockfile*(path: string): Lockfile =
result.entries = @[]
if not fileExists(path):
return result
let data = parseToml(path)
if data.hasKey("Package"):
let arr = data["Package"]
if arr.kind == tvkArray:
for item in arr.arrayVal:
if item.kind == tvkTable:
let t = item.tableVal
var entry = LockEntry()
if t.hasKey("Name"): entry.name = t["Name"].strVal
if t.hasKey("Version"): entry.version = t["Version"].strVal
if t.hasKey("Source"): entry.source = t["Source"].strVal
if t.hasKey("Checksum"): entry.checksum = t["Checksum"].strVal
result.entries.add(entry)
proc saveLockfile*(path: string, lock: Lockfile) =
var lines: seq[string] = @[]
for entry in lock.entries:
lines.add("[[Package]]")
lines.add(&"Name = \"{entry.name}\"")
lines.add(&"Version = \"{entry.version}\"")
lines.add(&"Source = \"{entry.source}\"")
if entry.checksum.len > 0:
lines.add(&"Checksum = \"{entry.checksum}\"")
lines.add("")
writeFile(path, lines.join("\n"))
# ---------------------------------------------------------------------------
# Manifest loader
# ---------------------------------------------------------------------------
proc loadManifest*(path: string): Manifest =
let data = parseSimpleToml(path)
let data = parseToml(path)
result.name = ""
result.version = "0.1.0"
result.pkgType = ptExecutable
result.output = "Bin"
result.dependencies = newOrderedTable[string, string]()
result.dependencies = @[]
result.devDependencies = @[]
result.buildDependencies = @[]
if data.hasKey("Package"):
let pkg = data["Package"].tableVal
@@ -77,3 +265,10 @@ proc loadManifest*(path: string): Manifest =
let bld = data["Build"].tableVal
if bld.hasKey("Output"):
result.output = bld["Output"].strVal
if data.hasKey("Dependencies"):
result.dependencies = parseDepTable(data["Dependencies"].tableVal)
if data.hasKey("DevDependencies"):
result.devDependencies = parseDepTable(data["DevDependencies"].tableVal)
if data.hasKey("BuildDependencies"):
result.buildDependencies = parseDepTable(data["BuildDependencies"].tableVal)
+22 -2
View File
@@ -212,6 +212,9 @@ proc parseBaseType(p: var Parser): TypeExpr =
return TypeExpr(kind: tekPointer, loc: loc, pointerPointee: p.parseBaseType())
of tkAmp:
discard p.advance()
if p.check(tkMut):
discard p.advance()
return TypeExpr(kind: tekMutRef, loc: loc, pointerPointee: p.parseBaseType())
return TypeExpr(kind: tekRef, loc: loc, pointerPointee: p.parseBaseType())
of tkLParen:
discard p.advance()
@@ -894,11 +897,27 @@ proc parseStmt(p: var Parser): Stmt =
# Declarations
# ---------------------------------------------------------------------------
proc parseTypeParams(p: var Parser): seq[string] =
proc parseTypeParams(p: var Parser): seq[TypeParam] =
if p.check(tkLt):
discard p.advance()
while not p.check(tkGt) and not p.isAtEnd:
result.add(p.expect(tkIdent, "expected type parameter name").text)
let name = p.expect(tkIdent, "expected type parameter name").text
var bounds: seq[string] = @[]
if p.check(tkColon):
discard p.advance()
# Parse bound: single identifier or path like Std::Comparable
var boundName = ""
while true:
let part = p.expect(tkIdent, "expected trait/interface name").text
if boundName.len > 0:
boundName.add("_")
boundName.add(part)
if p.check(tkColonColon):
discard p.advance()
else:
break
bounds.add(boundName)
result.add(TypeParam(name: name, bounds: bounds))
if p.check(tkComma):
discard p.advance()
discard p.expect(tkGt, "expected '>' to close type parameters")
@@ -1241,6 +1260,7 @@ proc parseDecl(p: var Parser): Decl =
var attrs = ParsedAttrs()
if p.check(tkAt):
attrs = p.parseAttrs()
p.skipNewlines()
var isConst = false
if p.check(tkConst) and p.peek(1) == tkFunc:
+242 -17
View File
@@ -20,6 +20,16 @@ type
params*: seq[Type]
retType*: Type
CtValueKind = enum
ctkVoid, ctkInt, ctkBool, ctkString
CtValue = object
case kind: CtValueKind
of ctkVoid: discard
of ctkInt: intVal: int64
of ctkBool: boolVal: bool
of ctkString: strVal: string
Sema* = object
module*: Module
globalScope*: Scope
@@ -30,6 +40,8 @@ type
methodTable*: Table[string, seq[MethodInfo]]
# Interface name -> interface decl
interfaceTable*: Table[string, Decl]
# Borrow checker state
checkedFunc*: bool ## true inside @[Checked] function
# ---------------------------------------------------------------------------
# Helpers
@@ -94,6 +106,16 @@ proc typeToTypeExpr(t: Type): TypeExpr =
TypeExpr(kind: tekPointer, pointerPointee: typeToTypeExpr(t.inner[0]))
else:
TypeExpr(kind: tekNamed, typeName: "void")
of tkRef:
if t.inner.len > 0:
TypeExpr(kind: tekRef, pointerPointee: typeToTypeExpr(t.inner[0]))
else:
TypeExpr(kind: tekNamed, typeName: "void")
of tkMutRef:
if t.inner.len > 0:
TypeExpr(kind: tekMutRef, pointerPointee: typeToTypeExpr(t.inner[0]))
else:
TypeExpr(kind: tekNamed, typeName: "void")
of tkVoid: TypeExpr(kind: tekNamed, typeName: "void")
else: TypeExpr(kind: tekNamed, typeName: t.toString)
@@ -102,7 +124,8 @@ proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type],
## Infer type arguments from argument types for a generic function call.
## Returns empty seq if inference fails for any type parameter.
result = @[]
for tpName in funcDecl.declFuncTypeParams:
for tp in funcDecl.declFuncTypeParams:
let tpName = tp.name
var inferred: Type = nil
for i, param in funcDecl.declFuncParams:
if i >= argTypes.len: break
@@ -173,9 +196,9 @@ proc resolveType(sema: var Sema, te: TypeExpr): Type =
of tekPointer:
return makePointer(sema.resolveType(te.pointerPointee))
of tekRef:
return makePointer(sema.resolveType(te.pointerPointee)) # &T → *T in bootstrap
return makeRef(sema.resolveType(te.pointerPointee))
of tekMutRef:
return makePointer(sema.resolveType(te.pointerPointee)) # &mut T → *T in bootstrap
return makeMutRef(sema.resolveType(te.pointerPointee))
of tekSlice:
let elemType = sema.resolveType(te.sliceElement)
return makeSlice(elemType)
@@ -191,6 +214,162 @@ proc resolveType(sema: var Sema, te: TypeExpr): Type =
# First pass: collect global symbols
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# Compile-Time Function Execution (CTFE)
# ---------------------------------------------------------------------------
proc evalExpr(sema: Sema, expr: Expr, locals: Table[string, CtValue]): CtValue
proc evalBlock(sema: Sema, blk: Block, locals: Table[string, CtValue]): CtValue =
var localVars = locals
for stmt in blk.stmts:
case stmt.kind
of skLet:
if stmt.stmtLetInit != nil:
let val = sema.evalExpr(stmt.stmtLetInit, localVars)
if val.kind in {ctkInt, ctkBool, ctkString}:
localVars[stmt.stmtLetName] = val
of skIf:
let cond = sema.evalExpr(stmt.stmtIfCond, localVars)
if cond.kind == ctkBool:
if cond.boolVal:
let res = sema.evalBlock(stmt.stmtIfThen, localVars)
if res.kind != ctkVoid:
return res
elif stmt.stmtIfElse != nil:
let res = sema.evalBlock(stmt.stmtIfElse, localVars)
if res.kind != ctkVoid:
return res
# If condition is false and no else, continue to next statement
else:
return CtValue(kind: ctkVoid)
of skReturn:
if stmt.stmtReturnValue != nil:
return sema.evalExpr(stmt.stmtReturnValue, localVars)
return CtValue(kind: ctkVoid)
of skExpr:
let res = sema.evalExpr(stmt.stmtExpr, localVars)
if res.kind != ctkVoid:
return res
else:
discard
return CtValue(kind: ctkVoid)
proc evalExpr(sema: Sema, expr: Expr, locals: Table[string, CtValue]): CtValue =
if expr == nil:
return CtValue(kind: ctkVoid)
case expr.kind
of ekLiteral:
case expr.exprLit.kind
of tkIntLiteral:
return CtValue(kind: ctkInt, intVal: parseBiggestInt(expr.exprLit.text))
of tkBoolLiteral:
return CtValue(kind: ctkBool, boolVal: expr.exprLit.text == "true")
of tkStringLiteral:
return CtValue(kind: ctkString, strVal: expr.exprLit.text)
else:
return CtValue(kind: ctkVoid)
of ekIdent:
if locals.hasKey(expr.exprIdent):
return locals[expr.exprIdent]
# Check if it's a const global
let sym = sema.globalScope.lookup(expr.exprIdent)
if sym != nil and sym.decl != nil and sym.decl.kind == dkConst and sym.decl.declConstValue != nil:
return sema.evalExpr(sym.decl.declConstValue, locals)
return CtValue(kind: ctkVoid)
of ekUnary:
let operand = sema.evalExpr(expr.exprUnaryOperand, locals)
case expr.exprUnaryOp
of tkMinus:
if operand.kind == ctkInt:
return CtValue(kind: ctkInt, intVal: -operand.intVal)
of tkBang:
if operand.kind == ctkBool:
return CtValue(kind: ctkBool, boolVal: not operand.boolVal)
else:
discard
return CtValue(kind: ctkVoid)
of ekBinary:
let left = sema.evalExpr(expr.exprBinaryLeft, locals)
let right = sema.evalExpr(expr.exprBinaryRight, locals)
if left.kind == ctkInt and right.kind == ctkInt:
case expr.exprBinaryOp
of tkPlus: return CtValue(kind: ctkInt, intVal: left.intVal + right.intVal)
of tkMinus: return CtValue(kind: ctkInt, intVal: left.intVal - right.intVal)
of tkStar: return CtValue(kind: ctkInt, intVal: left.intVal * right.intVal)
of tkSlash:
if right.intVal != 0:
return CtValue(kind: ctkInt, intVal: left.intVal div right.intVal)
of tkPercent:
if right.intVal != 0:
return CtValue(kind: ctkInt, intVal: left.intVal mod right.intVal)
of tkEq: return CtValue(kind: ctkBool, boolVal: left.intVal == right.intVal)
of tkNe: return CtValue(kind: ctkBool, boolVal: left.intVal != right.intVal)
of tkLt: return CtValue(kind: ctkBool, boolVal: left.intVal < right.intVal)
of tkLe: return CtValue(kind: ctkBool, boolVal: left.intVal <= right.intVal)
of tkGt: return CtValue(kind: ctkBool, boolVal: left.intVal > right.intVal)
of tkGe: return CtValue(kind: ctkBool, boolVal: left.intVal >= right.intVal)
else: discard
elif left.kind == ctkBool and right.kind == ctkBool:
case expr.exprBinaryOp
of tkAmpAmp: return CtValue(kind: ctkBool, boolVal: left.boolVal and right.boolVal)
of tkPipePipe: return CtValue(kind: ctkBool, boolVal: left.boolVal or right.boolVal)
else: discard
return CtValue(kind: ctkVoid)
of ekTernary:
let cond = sema.evalExpr(expr.exprTernaryCond, locals)
if cond.kind == ctkBool:
if cond.boolVal:
return sema.evalExpr(expr.exprTernaryThen, locals)
else:
return sema.evalExpr(expr.exprTernaryElse, locals)
return CtValue(kind: ctkVoid)
of ekCall:
# Try to evaluate const func calls
if expr.exprCallCallee != nil and expr.exprCallCallee.kind == ekIdent:
let funcName = expr.exprCallCallee.exprIdent
let sym = sema.globalScope.lookup(funcName)
if sym != nil and sym.decl != nil and sym.decl.kind == dkFunc and sym.decl.declFuncConst:
# Evaluate arguments
var argVals: seq[CtValue] = @[]
for arg in expr.exprCallArgs:
argVals.add(sema.evalExpr(arg, locals))
# Build parameter locals
var callLocals = locals
for i, p in sym.decl.declFuncParams:
if i < argVals.len:
callLocals[p.name] = argVals[i]
# Evaluate function body
if sym.decl.declFuncBody != nil:
return sema.evalBlock(sym.decl.declFuncBody, callLocals)
return CtValue(kind: ctkVoid)
of ekBlock:
return sema.evalBlock(expr.exprBlock, locals)
else:
return CtValue(kind: ctkVoid)
proc constFoldConstDecl(sema: Sema, decl: Decl): bool =
## Try to evaluate a const declaration at compile time.
## Returns true if successful and modifies declConstValue to a literal.
if decl.kind != dkConst: return false
let val = sema.evalExpr(decl.declConstValue, initTable[string, CtValue]())
case val.kind
of ctkInt:
decl.declConstValue = Expr(kind: ekLiteral, loc: decl.loc,
exprLit: Token(kind: tkIntLiteral, text: $val.intVal, loc: decl.loc))
return true
of ctkBool:
decl.declConstValue = Expr(kind: ekLiteral, loc: decl.loc,
exprLit: Token(kind: tkBoolLiteral, text: $val.boolVal, loc: decl.loc))
return true
of ctkString:
decl.declConstValue = Expr(kind: ekLiteral, loc: decl.loc,
exprLit: Token(kind: tkStringLiteral, text: val.strVal, loc: decl.loc))
return true
of ctkVoid:
return false
proc collectGlobals*(sema: var Sema) =
for decl in sema.module.items:
case decl.kind
@@ -200,8 +379,8 @@ proc collectGlobals*(sema: var Sema) =
# Temporarily add type parameters to type table for resolution
var addedTypeParams: seq[string] = @[]
for tp in decl.declFuncTypeParams:
sema.typeTable[tp] = makeTypeParam(tp)
addedTypeParams.add(tp)
sema.typeTable[tp.name] = makeTypeParam(tp.name)
addedTypeParams.add(tp.name)
# Build function type from params and return
var params: seq[Type] = @[]
for p in decl.declFuncParams:
@@ -337,8 +516,8 @@ proc collectGlobals*(sema: var Sema) =
# If impl has type params, temporarily add them to type table
var addedTypeParams: seq[string] = @[]
for tp in implTypeParams:
sema.typeTable[tp] = makeTypeParam(tp)
addedTypeParams.add(tp)
sema.typeTable[tp.name] = makeTypeParam(tp.name)
addedTypeParams.add(tp.name)
for methodDecl in decl.declImplMethods:
if methodDecl.kind == dkFunc:
# Propagate impl type params to method for HIR lowering
@@ -372,6 +551,10 @@ proc collectGlobals*(sema: var Sema) =
sema.typeTable.del(tp)
else:
discard
# Second pass: evaluate const declarations after all functions are registered
for decl in sema.module.items:
if decl.kind == dkConst:
discard sema.constFoldConstDecl(decl)
# ---------------------------------------------------------------------------
# Expression type checking
@@ -380,6 +563,36 @@ proc collectGlobals*(sema: var Sema) =
proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type
proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type
proc typeImplements(sema: Sema, t: Type, interfaceName: string): bool =
## Check if a type implements an interface by verifying all required methods exist.
if t.isUnknown: return true
let typeName = if t.kind == tkNamed: t.name elif t.isPointer and t.inner.len > 0 and t.inner[0].kind == tkNamed: t.inner[0].name else: ""
if typeName == "": return false
if not sema.interfaceTable.hasKey(interfaceName):
return true # Unknown interface — be permissive in bootstrap
let iface = sema.interfaceTable[interfaceName]
let requiredMethods = iface.declInterfaceMethods
if not sema.methodTable.hasKey(typeName):
return false
let availableMethods = sema.methodTable[typeName]
for req in requiredMethods:
var found = false
for avail in availableMethods:
if avail.name == req.declFuncName:
found = true
break
if not found:
return false
return true
proc checkTraitBounds(sema: var Sema, funcDecl: Decl, inferredTypes: seq[Type], loc: SourceLocation) =
## Verify that inferred types satisfy their trait bounds.
for i, tp in funcDecl.declFuncTypeParams:
if i < inferredTypes.len and inferredTypes[i] != nil:
for bound in tp.bounds:
if not sema.typeImplements(inferredTypes[i], bound):
sema.emitError(loc, &"type '{inferredTypes[i].toString}' does not implement trait '{bound}'")
proc extractPatternBindings(sema: var Sema, pat: Pattern, scope: Scope) =
## Add pattern-bound identifiers to scope with unknown type (best-effort)
if pat == nil: return
@@ -461,7 +674,7 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
return makeUnknown()
return operandType.inner[0]
of tkAmp:
return makePointer(operandType)
return makeMutRef(operandType)
else:
return operandType
of ekPostfix:
@@ -506,6 +719,11 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
let value = sema.checkExpr(expr.exprAssignValue, scope)
if not value.isAssignableTo(target):
sema.emitError(expr.loc, &"cannot assign {value.toString} to {target.toString}")
# Borrow check: cannot write through &T (shared reference) in @[Checked] functions
if sema.checkedFunc and expr.exprAssignTarget.kind == ekUnary and expr.exprAssignTarget.exprUnaryOp == tkStar:
let ptrType = sema.checkExpr(expr.exprAssignTarget.exprUnaryOperand, scope)
if ptrType.isRef:
sema.emitError(expr.loc, "cannot assign through shared reference '&T' in checked function — use '&mut T' instead")
return target
of ekTernary:
let cond = sema.checkExpr(expr.exprTernaryCond, scope)
@@ -542,7 +760,7 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
if sym2 != nil and sym2.decl != nil and sym2.decl.kind == dkFunc:
let typeParams = sym2.decl.declFuncTypeParams
for i, tp in typeParams:
if retType.name == tp and i < expr.exprCallCallee.exprGenericTypeArgs.len:
if retType.name == tp.name and i < expr.exprCallCallee.exprGenericTypeArgs.len:
# Substitute with concrete type
let concreteType = expr.exprCallCallee.exprGenericTypeArgs[i]
if concreteType.kind == tekNamed:
@@ -622,14 +840,19 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
let inferred = sema.inferTypeArgs(calleeDecl, argTypes, expr.loc)
if inferred.len == calleeDecl.declFuncTypeParams.len:
expr.exprCallInferredTypeArgs = inferred
# Check trait bounds
var inferredTypes: seq[Type] = @[]
for te in inferred:
inferredTypes.add(sema.resolveType(te))
sema.checkTraitBounds(calleeDecl, inferredTypes, expr.loc)
# Substitute return type using inferred type args
if calleeDecl.declFuncReturnType != nil:
var added: seq[string] = @[]
for i, tp in calleeDecl.declFuncTypeParams:
if i < inferred.len:
let concrete = sema.resolveType(inferred[i])
sema.typeTable[tp] = concrete
added.add(tp)
sema.typeTable[tp.name] = concrete
added.add(tp.name)
let retType = sema.resolveType(calleeDecl.declFuncReturnType)
for tp in added:
sema.typeTable.del(tp)
@@ -666,8 +889,8 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
of ekField:
let obj = sema.checkExpr(expr.exprFieldObj, scope)
var objType = obj
# Auto-dereference pointer types for field access
if objType.kind == tkPointer and objType.inner.len > 0:
# Auto-dereference pointer/reference types for field access
if objType.kind in {tkPointer, tkRef, tkMutRef} and objType.inner.len > 0:
objType = objType.inner[0]
if objType.kind == tkNamed:
# Check if this is a _Data union field access
@@ -858,7 +1081,6 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
else:
discard
return makeVoid()
# ---------------------------------------------------------------------------
# Function body checking
# ---------------------------------------------------------------------------
@@ -870,12 +1092,14 @@ proc checkFunc(sema: var Sema, decl: Decl) =
# type parameters that cannot be fully resolved until monomorphization.
if decl.declFuncTypeParams.len > 0:
return
let wasChecked = sema.checkedFunc
sema.checkedFunc = "Checked" in decl.declAttrs
var funcScope = newScope(sema.globalScope)
# Add type parameters to type table for resolution
var addedTypeParams: seq[string] = @[]
for tp in decl.declFuncTypeParams:
sema.typeTable[tp] = makeTypeParam(tp)
addedTypeParams.add(tp)
sema.typeTable[tp.name] = makeTypeParam(tp.name)
addedTypeParams.add(tp.name)
# Add parameters
for p in decl.declFuncParams:
let pType = sema.resolveType(p.ptype)
@@ -887,6 +1111,7 @@ proc checkFunc(sema: var Sema, decl: Decl) =
# Clean up type parameters
for tp in addedTypeParams:
sema.typeTable.del(tp)
sema.checkedFunc = wasChecked
# ---------------------------------------------------------------------------
# Second pass: check all function bodies
@@ -898,7 +1123,7 @@ proc checkBodies(sema: var Sema) =
var funcCount = 0
for decl in sema.module.items:
if decl.kind == dkFunc: inc funcCount
if funcCount > 50:
if funcCount > 5000:
# Large module — only check Main
for decl in sema.module.items:
case decl.kind
+4 -1
View File
@@ -50,6 +50,7 @@ type
tkSuper # super
tkSizeOf # sizeof
tkOwn # own (gradual ownership transfer)
tkMut # mut (mutable reference)
tkDiscard # discard (evaluate and throw away)
##Punctuation
@@ -142,7 +143,7 @@ proc isKeyword*(kind: TokenKind): bool =
tkBreak, tkContinue, tkReturn, tkMatch,
tkFunc, tkLet, tkVar, tkConst, tkType, tkStruct, tkEnum,
tkUnion, tkInterface, tkExtend, tkModule, tkImport,
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkDiscard:
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkMut, tkDiscard:
true
else:
false
@@ -197,6 +198,7 @@ proc keywordKind*(text: string): TokenKind =
of "super": tkSuper
of "sizeof": tkSizeOf
of "own": tkOwn
of "mut": tkMut
of "discard": tkDiscard
of "true", "false": tkBoolLiteral
else: tkIdent
@@ -242,6 +244,7 @@ proc tokenKindName*(kind: TokenKind): string =
of tkSelf: "'self'"
of tkSuper: "'super'"
of tkOwn: "'own'"
of tkMut: "'mut'"
of tkDiscard: "'discard'"
of tkLParen: "'('"
of tkRParen: "')'"
+24 -1
View File
@@ -25,6 +25,8 @@ type
tkFloat32
tkFloat64
tkPointer
tkRef
tkMutRef
tkSlice
tkRange
tkTuple
@@ -63,6 +65,10 @@ proc makeFloat64*(): Type = Type(kind: tkFloat64)
proc makePointer*(pointee: Type): Type =
Type(kind: tkPointer, inner: @[pointee])
proc makeRef*(pointee: Type): Type =
Type(kind: tkRef, inner: @[pointee])
proc makeMutRef*(pointee: Type): Type =
Type(kind: tkMutRef, inner: @[pointee])
proc makeSlice*(element: Type): Type =
Type(kind: tkSlice, inner: @[element])
proc makeRange*(element: Type): Type =
@@ -95,7 +101,10 @@ proc isFloat*(t: Type): bool =
proc isSigned*(t: Type): bool =
if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt}
proc isPointer*(t: Type): bool = t.kind == tkPointer
proc isPointer*(t: Type): bool = t.kind in {tkPointer, tkRef, tkMutRef}
proc isRawPointer*(t: Type): bool = t.kind == tkPointer
proc isRef*(t: Type): bool = t.kind == tkRef
proc isMutRef*(t: Type): bool = t.kind == tkMutRef
proc isSlice*(t: Type): bool = t.kind == tkSlice
# Comparison
@@ -139,6 +148,18 @@ proc isAssignableTo*(a, b: Type): bool =
return true
if b.inner.len > 0 and b.inner[0].isUnknown:
return true
# &mut T -> &T (mutable ref can coerce to shared ref)
if a.isMutRef and b.isRef:
if a.inner.len > 0 and b.inner.len > 0 and a.inner[0].isAssignableTo(b.inner[0]):
return true
# &mut T -> *T (mutable ref can coerce to raw pointer)
if a.isMutRef and b.isRawPointer:
if a.inner.len > 0 and b.inner.len > 0 and a.inner[0].isAssignableTo(b.inner[0]):
return true
# &T -> *T (shared ref can coerce to raw pointer)
if a.isRef and b.isRawPointer:
if a.inner.len > 0 and b.inner.len > 0 and a.inner[0].isAssignableTo(b.inner[0]):
return true
return false
# String representation
@@ -167,6 +188,8 @@ proc toString*(t: Type): string =
of tkFloat32: "float32"
of tkFloat64: "float64"
of tkPointer: "*" & t.inner[0].toString
of tkRef: "&" & t.inner[0].toString
of tkMutRef: "&mut " & t.inner[0].toString
of tkSlice:
if t.inner.len > 0: t.inner[0].toString & "[]"
else: "Slice<?>"