From 4bcb706d496e4dce3f25040e67065950973fbaa2 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 28 Apr 2026 18:48:41 +0200 Subject: [PATCH] IC: added support for conditional dependencies (#25770) --- compiler/deps.nim | 215 ++++++++++++++++++++++++++++++++++++++++------ compiler/nim.cfg | 3 +- koch.nim | 3 +- 3 files changed, 194 insertions(+), 27 deletions(-) diff --git a/compiler/deps.nim b/compiler/deps.nim index 18d6608fd0..116c0ebdf3 100644 --- a/compiler/deps.nim +++ b/compiler/deps.nim @@ -137,6 +137,171 @@ proc processImport(c: var DepContext; importPath: string; current: Node) = if existingIdx notin current.deps: current.deps.add existingIdx +proc skipSubtree(s: var Stream; first: PackedToken) = + ## Consume tokens until the ParLe at `first` is balanced. Caller has + ## already obtained `first`. + if first.kind != ParLe: return + var depth = 1 + while depth > 0: + let t = next(s) + if t.kind == ParLe: inc depth + elif t.kind == ParRi: dec depth + elif t.kind == EofToken: return + +proc evalCondExpr(c: DepContext; s: var Stream): bool = + ## Read exactly one condition expression from `s` and return its truth + ## value. Consumes tokens whether the expression is recognised or not so + ## the caller stays in sync. Recognises `defined(IDENT)`, the boolean + ## operators `not`/`and`/`or`, and the literals `true`/`false`. Anything + ## else (e.g. a call to an arbitrary proc) is treated as `true` — the + ## conservative direction, since a false negative here drops a real + ## dependency from the build graph. + let t = next(s) + case t.kind + of Ident: + case pool.strings[t.litId] + of "true": result = true + of "false": result = false + else: result = true + of ParLe: + let tag = pool.tags[t.tagId] + case tag + of "call", "cmd", "callstrlit", "infix", "prefix": + # First child is the head (function/operator name). + let head = next(s) + var name = "" + if head.kind == Ident: name = pool.strings[head.litId] + case name + of "defined": + let arg = next(s) + var sym = "" + if arg.kind == Ident: sym = pool.strings[arg.litId] + result = sym.len > 0 and isDefined(c.config, sym) + of "not": + result = not evalCondExpr(c, s) + of "and": + result = evalCondExpr(c, s) + if result: result = evalCondExpr(c, s) + else: skipSubtree(s, next(s)) + of "or": + result = evalCondExpr(c, s) + if not result: result = evalCondExpr(c, s) + else: skipSubtree(s, next(s)) + else: + result = true + # Drain whatever remains until the matching ParRi. + var depth = 1 + while depth > 0: + let n = next(s) + if n.kind == ParLe: inc depth + elif n.kind == ParRi: dec depth + elif n.kind == EofToken: return + of "not": + result = not evalCondExpr(c, s) + var depth = 1 + while depth > 0: + let n = next(s) + if n.kind == ParLe: inc depth + elif n.kind == ParRi: dec depth + elif n.kind == EofToken: return + of "and": + result = evalCondExpr(c, s) + if result: result = evalCondExpr(c, s) + else: skipSubtree(s, next(s)) + # consume closing ParRi + var depth = 1 + while depth > 0: + let n = next(s) + if n.kind == ParLe: inc depth + elif n.kind == ParRi: dec depth + elif n.kind == EofToken: return + of "or": + result = evalCondExpr(c, s) + if not result: result = evalCondExpr(c, s) + else: skipSubtree(s, next(s)) + var depth = 1 + while depth > 0: + let n = next(s) + if n.kind == ParLe: inc depth + elif n.kind == ParRi: dec depth + elif n.kind == EofToken: return + else: + skipSubtree(s, t) + result = true + else: + result = true + +proc whenMarkerHolds(c: DepContext; s: var Stream): bool = + ## Caller has just consumed the `(when` ParLe. Read children until the + ## matching `)`, AND-ing each evaluated condition. + result = true + while true: + # peek by reading; if it's ParRi, we're done + let t = next(s) + if t.kind == ParRi: return + if t.kind == EofToken: return + if t.kind == ParLe: + # Re-feed by manually evaluating the subtree starting at `t`. + # evalCondExpr expects to read its own opener, so handle it directly. + let tag = pool.tags[t.tagId] + case tag + of "call", "cmd", "callstrlit", "infix", "prefix": + let head = next(s) + var name = "" + if head.kind == Ident: name = pool.strings[head.litId] + var ok = true + case name + of "defined": + let arg = next(s) + var sym = "" + if arg.kind == Ident: sym = pool.strings[arg.litId] + ok = sym.len > 0 and isDefined(c.config, sym) + of "not": + ok = not evalCondExpr(c, s) + of "and": + ok = evalCondExpr(c, s) + if ok: ok = evalCondExpr(c, s) + of "or": + ok = evalCondExpr(c, s) + if not ok: ok = evalCondExpr(c, s) + else: + ok = true + # finish the subtree + var depth = 1 + while depth > 0: + let n = next(s) + if n.kind == ParLe: inc depth + elif n.kind == ParRi: dec depth + elif n.kind == EofToken: return + if not ok: result = false + of "not", "and", "or": + # Re-emit a synthetic dispatch: rewrap by descending. + var ok = true + case tag + of "not": + ok = not evalCondExpr(c, s) + of "and": + ok = evalCondExpr(c, s) + if ok: ok = evalCondExpr(c, s) + of "or": + ok = evalCondExpr(c, s) + if not ok: ok = evalCondExpr(c, s) + else: discard + var depth = 1 + while depth > 0: + let n = next(s) + if n.kind == ParLe: inc depth + elif n.kind == ParRi: dec depth + elif n.kind == EofToken: return + if not ok: result = false + else: + # Unknown — treat as true and skip. + skipSubtree(s, t) + elif t.kind == Ident: + let v = pool.strings[t.litId] + if v == "false": result = false + # else (true / unknown ident): keep result + proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) = ## Read a .deps.nif file and process imports/includes let depsPath = c.depsFile(pair) @@ -158,12 +323,27 @@ proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) = if t.kind == ParLe: let tag = pool.tags[t.tagId] case tag - of "import", "fromimport": - # Read import path + of "import", "fromimport", "include": + # Read first child. May be a `(when COND...)` marker — parse and + # evaluate; if the condition is statically false, skip the import + # entirely. Otherwise advance past the marker and parse the path. t = next(s) - # Check for "when" marker (conditional import) - if t.kind == Ident and pool.strings[t.litId] == "when": - t = next(s) # skip it, still process the import + var live = true + if t.kind == ParLe and pool.tags[t.tagId] == "when": + # whenMarkerHolds consumes everything up to and including the + # closing `)` of the `(when ...)` subtree. + live = whenMarkerHolds(c, s) + t = next(s) + if not live: + # Drain the rest of this import/include node. + var depth = 1 + while depth > 0: + let n = next(s) + if n.kind == ParLe: inc depth + elif n.kind == ParRi: dec depth + elif n.kind == EofToken: break + t = next(s) + continue # Handle path expression (could be ident, string, or infix like std/foo) var importPath = "" if t.kind == Ident: @@ -181,26 +361,11 @@ proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) = if t.kind == Ident: # second part (foo) importPath = importPath & "/" & pool.strings[t.litId] if importPath.len > 0: - processImport(c, importPath, current) - # Skip to end of import node - var depth = 1 - while depth > 0: - t = next(s) - if t.kind == ParLe: inc depth - elif t.kind == ParRi: dec depth - of "include": - # Read include path - t = next(s) - if t.kind == Ident and pool.strings[t.litId] == "when": - t = next(s) # skip conditional marker - var includePath = "" - if t.kind == Ident: - includePath = pool.strings[t.litId] - elif t.kind == StringLit: - includePath = pool.strings[t.litId] - if includePath.len > 0: - processInclude(c, includePath, current) - # Skip to end + if tag == "include": + processInclude(c, importPath, current) + else: + processImport(c, importPath, current) + # Skip to end of node var depth = 1 while depth > 0: t = next(s) diff --git a/compiler/nim.cfg b/compiler/nim.cfg index 425f0df324..19c1df344f 100644 --- a/compiler/nim.cfg +++ b/compiler/nim.cfg @@ -12,7 +12,8 @@ define:nimPreviewNonVarDestructor define:nimPreviewCheckedClose define:nimPreviewAsmSemSymbol define:nimPreviewCStringComparisons -define:nimPreviewDuplicateModuleError +#define:nimPreviewDuplicateModuleError +# Incompatible with Nimony's compat2.nim for now threads:off diff --git a/koch.nim b/koch.nim index 0fd0e365c9..c9269303b1 100644 --- a/koch.nim +++ b/koch.nim @@ -16,10 +16,11 @@ const ChecksumsStableCommit = "0b8e46379c5bc1bf73d8b3011908389c60fb9b98" # 2.0.1 SatStableCommit = "e63eaea8baf00bed8bcd5a29ffd8823abb265b39" - NimonyStableCommit = "bbfb21529845567c55b67d176354daef0e7d6c29" # unversioned \ + NimonyStableCommit = "c189ef438598878b2f02f6a2ff91d08febafc04b" # unversioned \ # Note that Nimony uses Nim as a git submodule but we don't want to install # Nimony's dependency to Nim as we are Nim. So a `git clone` without --recursive # is **required** here. + # Commit from 2026-04-27 # examples of possible values for fusion: #head, #ea82b54, 1.2.3 FusionStableHash = "#562467452b32cb7a97410ea177f083e6d8405734"