feat: macros (multi-rep, hygiene), Drop field-move, lean multi-OS CI
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
Sessions 56–69: declarative macro! with rep/zip/literal/block and unhygienic var $name binders; partial field-move skip Drop; @[Release] polish; LSP type hierarchy; CI Nim cache + lean macOS + Windows smoke.
This commit is contained in:
+65
-4
@@ -17,6 +17,8 @@
|
||||
# v0.13.0: textDocument/implementation (interface → types / methods).
|
||||
# v0.14.0: workspace-wide import path index (no open-doc required).
|
||||
# v0.15.0: type hierarchy (prepare / supertypes / subtypes via extend for).
|
||||
# v0.16.0: workspace type-impl index — hierarchy works for closed multi-file
|
||||
# docs even when `extend T for I` has no methods (no open required).
|
||||
|
||||
import std/[json, os, strutils, streams, tables, osproc, sequtils, sets]
|
||||
import lexer, parser, ast, sema, types, scope, source_location
|
||||
@@ -154,6 +156,10 @@ var
|
||||
## Import paths by file URI (from scanWorkspace + open docs) — v0.14
|
||||
## Each entry is a full path like @["Std", "Io"] (not open-doc dependent).
|
||||
workspaceImportPaths = initTable[string, seq[seq[string]]]()
|
||||
## Type ↔ interface relations from `extend Type for Iface` (v0.16).
|
||||
## Keyed by file URI so re-analyze replaces stale entries (open or closed).
|
||||
## Does not require methods in the extend body (unlike workspaceImpls).
|
||||
workspaceTypeRels = initTable[string, seq[tuple[typeName, iface: string, line: int]]]()
|
||||
cachedStdlibDir = ""
|
||||
cachedStdlibDecls: seq[Decl] = @[]
|
||||
stdlibLoaded = false
|
||||
@@ -170,6 +176,11 @@ proc registerWorkspaceImports(uri: string, segs: seq[PathSegInfo]) =
|
||||
paths.add(s.path)
|
||||
workspaceImportPaths[uri] = paths
|
||||
|
||||
proc registerWorkspaceTypeRels(uri: string, impls: seq[tuple[typeName, iface: string, line: int]]) =
|
||||
## Replace type↔interface relations for this URI (from analyzeFile `impls`).
|
||||
## Empty impls clears prior entries so deleted extends disappear from hierarchy.
|
||||
workspaceTypeRels[uri] = impls
|
||||
|
||||
proc getDoc(uri: string): DocumentState =
|
||||
if not documents.hasKey(uri):
|
||||
documents[uri] = DocumentState(uri: uri)
|
||||
@@ -630,6 +641,8 @@ proc analyzeFile(path: string, content: string): DocumentState =
|
||||
|
||||
# Always refresh workspace import index for this URI (empty clears stale paths)
|
||||
registerWorkspaceImports(result.uri, result.importPaths)
|
||||
# Type hierarchy / implementation: keep extend-for relations for closed files
|
||||
registerWorkspaceTypeRels(result.uri, result.impls)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Real sema types for hover
|
||||
@@ -2631,8 +2644,30 @@ proc collectImplementorFuncs(iface, meth: string): seq[FuncSym] =
|
||||
|
||||
proc collectTypeImplementorLocs(iface: string): seq[JsonNode] =
|
||||
## Locations of types that `extend Type for iface`.
|
||||
## Uses workspace type-rel index so closed multi-file works without methods.
|
||||
result = @[]
|
||||
var seen = initHashSet[string]()
|
||||
# 1) Workspace type relations
|
||||
for uri, rels in workspaceTypeRels.pairs:
|
||||
for impl in rels:
|
||||
if impl.iface != iface: continue
|
||||
let key = uri & "#" & impl.typeName
|
||||
if seen.contains(key): continue
|
||||
seen.incl(key)
|
||||
if workspaceSymbols.hasKey(impl.typeName):
|
||||
let ws = workspaceSymbols[impl.typeName]
|
||||
result.add(locationJson(ws.uri, ws.info.line, ws.info.col, impl.typeName.len))
|
||||
elif documents.hasKey(uri):
|
||||
let doc = documents[uri]
|
||||
ensureAnalyzed(doc)
|
||||
if doc.symbols.hasKey(impl.typeName):
|
||||
let info = doc.symbols[impl.typeName]
|
||||
result.add(locationJson(uri, info.line, info.col, impl.typeName.len))
|
||||
else:
|
||||
result.add(locationJson(uri, impl.line, 0, max(1, impl.typeName.len)))
|
||||
else:
|
||||
result.add(locationJson(uri, impl.line, 0, max(1, impl.typeName.len)))
|
||||
# 2) Open docs
|
||||
for uri, doc in documents.pairs:
|
||||
ensureAnalyzed(doc)
|
||||
for impl in doc.impls:
|
||||
@@ -2644,9 +2679,8 @@ proc collectTypeImplementorLocs(iface: string): seq[JsonNode] =
|
||||
let info = doc.symbols[impl.typeName]
|
||||
result.add(locationJson(uri, info.line, info.col, impl.typeName.len))
|
||||
else:
|
||||
# Fall back to the `extend` line
|
||||
result.add(locationJson(uri, impl.line, 0, max(1, impl.typeName.len)))
|
||||
# Derive types from workspaceImpls (Iface.Method → typeName)
|
||||
# 3) Fallback: workspaceImpls (Iface.Method → typeName)
|
||||
for wkey, impls in workspaceImpls.pairs:
|
||||
if not wkey.startsWith(iface & "."): continue
|
||||
for impl in impls:
|
||||
@@ -2990,8 +3024,22 @@ proc typeHierarchyItemSynthetic(uri: string, name: string, kind: string, line: i
|
||||
|
||||
proc collectSubtypeItems(iface: string): seq[JsonNode] =
|
||||
## Types that `extend Type for iface`.
|
||||
## Prefer workspace type-rel index (closed multi-file; empty extend bodies OK).
|
||||
result = @[]
|
||||
var seen = initHashSet[string]()
|
||||
# 1) Workspace type relations (scan + every analyzeFile) — no open required
|
||||
for uri, rels in workspaceTypeRels.pairs:
|
||||
for impl in rels:
|
||||
if impl.iface != iface: continue
|
||||
let key = uri & "#" & impl.typeName
|
||||
if seen.contains(key): continue
|
||||
seen.incl(key)
|
||||
let (ok, u, info) = resolveTypeSymbol(impl.typeName, uri)
|
||||
if ok:
|
||||
result.add(typeHierarchyItem(u, impl.typeName, info))
|
||||
else:
|
||||
result.add(typeHierarchyItemSynthetic(uri, impl.typeName, "struct", impl.line))
|
||||
# 2) Open docs (live buffer may differ from last register)
|
||||
for uri, doc in documents.pairs:
|
||||
ensureAnalyzed(doc)
|
||||
for impl in doc.impls:
|
||||
@@ -3004,7 +3052,7 @@ proc collectSubtypeItems(iface: string): seq[JsonNode] =
|
||||
result.add(typeHierarchyItem(u, impl.typeName, info))
|
||||
else:
|
||||
result.add(typeHierarchyItemSynthetic(uri, impl.typeName, "struct", impl.line))
|
||||
# workspaceImpls: "Iface.Method" → (uri, typeName, meth)
|
||||
# 3) Fallback: workspaceImpls "Iface.Method" (methods in extend body)
|
||||
for wkey, impls in workspaceImpls.pairs:
|
||||
if not wkey.startsWith(iface & "."): continue
|
||||
for impl in impls:
|
||||
@@ -3021,6 +3069,18 @@ proc collectSupertypeItems(typeName: string): seq[JsonNode] =
|
||||
## Interfaces that `typeName` implements via `extend typeName for I`.
|
||||
result = @[]
|
||||
var seen = initHashSet[string]()
|
||||
# 1) Workspace type relations (closed multi-file)
|
||||
for uri, rels in workspaceTypeRels.pairs:
|
||||
for impl in rels:
|
||||
if impl.typeName != typeName: continue
|
||||
if seen.contains(impl.iface): continue
|
||||
seen.incl(impl.iface)
|
||||
let (ok, u, info) = resolveTypeSymbol(impl.iface, uri)
|
||||
if ok:
|
||||
result.add(typeHierarchyItem(u, impl.iface, info))
|
||||
else:
|
||||
result.add(typeHierarchyItemSynthetic(uri, impl.iface, "interface", impl.line))
|
||||
# 2) Open docs
|
||||
for uri, doc in documents.pairs:
|
||||
ensureAnalyzed(doc)
|
||||
for impl in doc.impls:
|
||||
@@ -3032,6 +3092,7 @@ proc collectSupertypeItems(typeName: string): seq[JsonNode] =
|
||||
result.add(typeHierarchyItem(u, impl.iface, info))
|
||||
else:
|
||||
result.add(typeHierarchyItemSynthetic(uri, impl.iface, "interface", impl.line))
|
||||
# 3) Fallback: method-based workspaceImpls
|
||||
for wkey, impls in workspaceImpls.pairs:
|
||||
for impl in impls:
|
||||
if impl.typeName != typeName: continue
|
||||
@@ -3137,7 +3198,7 @@ proc handleMessage(stream: FileStream, msg: JsonNode) =
|
||||
"implementationProvider": true,
|
||||
"typeHierarchyProvider": true
|
||||
},
|
||||
"serverInfo": {"name": "bux-lsp", "version": "0.15.0"}
|
||||
"serverInfo": {"name": "bux-lsp", "version": "0.16.0"}
|
||||
})
|
||||
if paramsNode.hasKey("rootPath") and paramsNode["rootPath"].kind != JNull:
|
||||
rootPath = paramsNode["rootPath"].getStr()
|
||||
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
# Golden-ish smoke: field-move + partial field-move Drop emission.
|
||||
# Ensures C for TakeItems has no Bag_Drop (would double-free returned Array).
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
BUXC="${BUXC:-$ROOT/buxc}"
|
||||
export BUX_STDLIB="${BUX_STDLIB:-$ROOT/lib}"
|
||||
unset BUX_DEBUG_FILE || true
|
||||
|
||||
if [[ ! -x "$BUXC" ]]; then
|
||||
(cd "$ROOT" && make build)
|
||||
fi
|
||||
|
||||
TMP=$(mktemp -d)
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
# --- move_field (whole local into field) ---
|
||||
echo "=== smoke: move_field ==="
|
||||
mkdir -p "$TMP/mf/src"
|
||||
cp -a "$ROOT/rt" "$TMP/mf/"
|
||||
cat > "$TMP/mf/bux.toml" <<'EOF'
|
||||
[Package]
|
||||
Name = "move_field"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
EOF
|
||||
cp "$ROOT/examples/move_field.bux" "$TMP/mf/src/Main.bux"
|
||||
(cd "$TMP/mf" && "$BUXC" run .)
|
||||
# Only the MakeBox *body* (not prototypes / other functions)
|
||||
if sed -n '/^Box MakeBox(void) {/,/^}/p' "$TMP/mf/build/main.c" | grep -q 'Array_Drop\|Bag_Drop'; then
|
||||
echo "error: MakeBox still drops moved Array" >&2
|
||||
sed -n '/^Box MakeBox(void) {/,/^}/p' "$TMP/mf/build/main.c"
|
||||
exit 1
|
||||
fi
|
||||
echo " move_field: PASS (run + no Array_Drop of moved local)"
|
||||
|
||||
# --- partial field move ---
|
||||
echo "=== smoke: move_field_partial ==="
|
||||
mkdir -p "$TMP/mp/src"
|
||||
cp -a "$ROOT/rt" "$TMP/mp/"
|
||||
cat > "$TMP/mp/bux.toml" <<'EOF'
|
||||
[Package]
|
||||
Name = "move_field_partial"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
EOF
|
||||
cp "$ROOT/examples/move_field_partial.bux" "$TMP/mp/src/Main.bux"
|
||||
(cd "$TMP/mp" && "$BUXC" run .)
|
||||
# TakeItems must not call Bag_Drop after moving bag.items out
|
||||
if sed -n '/^Array_int TakeItems/,/^}/p' "$TMP/mp/build/main.c" | grep -q 'Bag_Drop'; then
|
||||
echo "error: TakeItems still Bag_Drops after partial field move" >&2
|
||||
sed -n '/^Array_int TakeItems/,/^}/p' "$TMP/mp/build/main.c"
|
||||
exit 1
|
||||
fi
|
||||
echo " move_field_partial: PASS (run + TakeItems has no Bag_Drop)"
|
||||
|
||||
# --- early return Drop counts ---
|
||||
echo "=== smoke: drop_early_return ==="
|
||||
mkdir -p "$TMP/de/src"
|
||||
cp -a "$ROOT/rt" "$TMP/de/"
|
||||
cat > "$TMP/de/bux.toml" <<'EOF'
|
||||
[Package]
|
||||
Name = "drop_early_return"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
EOF
|
||||
cp "$ROOT/examples/drop_early_return.bux" "$TMP/de/src/Main.bux"
|
||||
out=$(cd "$TMP/de" && "$BUXC" run .)
|
||||
echo "$out" | grep -q 'PASS'
|
||||
echo " drop_early_return: PASS"
|
||||
|
||||
echo "PASS: smoke_drop_move (field-move + partial + early-return)"
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Smoke: type hierarchy prepare / subtypes / supertypes (bux-lsp 0.15)
|
||||
# Smoke: type hierarchy prepare / subtypes / supertypes (single-file; bux-lsp 0.15+)
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
LSP="$ROOT/tools/bux-lsp"
|
||||
@@ -62,8 +62,8 @@ URI="file://$TMP/Main.bux"
|
||||
rpc '{"jsonrpc":"2.0","method":"exit","params":null}'
|
||||
} | "$LSP" 2>/dev/null | tr '\r' '\n' > "$TMP/out.txt"
|
||||
|
||||
if ! grep -q '0.15.0' "$TMP/out.txt"; then
|
||||
echo "WARN: version not 0.15.0"
|
||||
if ! grep -qE '0\.(15|16)\.0' "$TMP/out.txt"; then
|
||||
echo "WARN: unexpected LSP version (expected 0.15+)"
|
||||
fi
|
||||
|
||||
if ! grep -q 'typeHierarchyProvider' "$TMP/out.txt"; then
|
||||
@@ -120,5 +120,5 @@ if 'Drawable' not in snames:
|
||||
sys.exit(1)
|
||||
print(f' supertypes Circle → {sorted(snames)}')
|
||||
|
||||
print('PASS: LSP type hierarchy (0.15)')
|
||||
print('PASS: LSP type hierarchy (single-file)')
|
||||
PY
|
||||
|
||||
Executable
+158
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env bash
|
||||
# Smoke: type hierarchy across closed multi-file workspace (bux-lsp 0.16)
|
||||
# Only Main.bux is opened. Drawable.bux + Shapes.bux stay closed (scanWorkspace).
|
||||
# Empty `extend T for I {}` bodies — no methods — must still populate hierarchy
|
||||
# via workspaceTypeRels (not method-only workspaceImpls).
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
LSP="$ROOT/tools/bux-lsp"
|
||||
TMP=$(mktemp -d)
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
if [[ ! -x "$LSP" ]]; then
|
||||
(cd "$ROOT" && make lsp >/dev/null)
|
||||
fi
|
||||
|
||||
cat > "$TMP/Drawable.bux" <<'EOF'
|
||||
interface Drawable {
|
||||
func Draw(self: &Self);
|
||||
}
|
||||
interface Named {
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > "$TMP/Shapes.bux" <<'EOF'
|
||||
struct Circle {
|
||||
radius: int;
|
||||
}
|
||||
struct Square {
|
||||
side: int;
|
||||
}
|
||||
// Empty extend bodies — no methods; relation must still be indexed
|
||||
extend Circle for Drawable {
|
||||
}
|
||||
extend Square for Drawable {
|
||||
}
|
||||
extend Circle for Named {
|
||||
}
|
||||
EOF
|
||||
|
||||
# Only this file is opened. Types are closed on disk.
|
||||
cat > "$TMP/Main.bux" <<'EOF'
|
||||
func Use(d: Drawable, c: Circle) -> int {
|
||||
return 0;
|
||||
}
|
||||
func Main() -> int {
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
rpc() {
|
||||
local body="$1"
|
||||
local len
|
||||
len=$(printf '%s' "$body" | wc -c)
|
||||
printf 'Content-Length: %s\r\n\r\n%s' "$len" "$body"
|
||||
}
|
||||
|
||||
CONTENT_JSON=$(python3 -c 'import json,sys; print(json.dumps(open(sys.argv[1]).read()))' "$TMP/Main.bux")
|
||||
URI="file://$TMP/Main.bux"
|
||||
# Drawable at col 12, Circle at col 25 in "func Use(d: Drawable, c: Circle)..."
|
||||
DRAW_COL=12
|
||||
CIRC_COL=25
|
||||
|
||||
{
|
||||
rpc '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},"rootUri":"file://'"$TMP"'"}}'
|
||||
rpc '{"jsonrpc":"2.0","method":"initialized","params":{}}'
|
||||
rpc '{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"'"$URI"'","languageId":"bux","version":1,"text":'"$CONTENT_JSON"'}}}'
|
||||
# prepare on Drawable (type annotation; def in closed Drawable.bux)
|
||||
rpc '{"jsonrpc":"2.0","id":2,"method":"textDocument/prepareTypeHierarchy","params":{"textDocument":{"uri":"'"$URI"'"},"position":{"line":0,"character":'"$DRAW_COL"'}}}'
|
||||
# subtypes of Drawable → Circle + Square (closed Shapes.bux, empty extends)
|
||||
rpc '{"jsonrpc":"2.0","id":3,"method":"typeHierarchy/subtypes","params":{"item":{"name":"Drawable","kind":11,"uri":"file://'"$TMP"'/Drawable.bux","data":"Drawable","range":{"start":{"line":0,"character":0},"end":{"line":0,"character":8}},"selectionRange":{"start":{"line":0,"character":0},"end":{"line":0,"character":8}}}}}'
|
||||
# prepare on Circle
|
||||
rpc '{"jsonrpc":"2.0","id":4,"method":"textDocument/prepareTypeHierarchy","params":{"textDocument":{"uri":"'"$URI"'"},"position":{"line":0,"character":'"$CIRC_COL"'}}}'
|
||||
# supertypes of Circle → Drawable + Named
|
||||
rpc '{"jsonrpc":"2.0","id":5,"method":"typeHierarchy/supertypes","params":{"item":{"name":"Circle","kind":23,"uri":"file://'"$TMP"'/Shapes.bux","data":"Circle","range":{"start":{"line":0,"character":0},"end":{"line":0,"character":6}},"selectionRange":{"start":{"line":0,"character":0},"end":{"line":0,"character":6}}}}}'
|
||||
# subtypes of Named → Circle only
|
||||
rpc '{"jsonrpc":"2.0","id":6,"method":"typeHierarchy/subtypes","params":{"item":{"name":"Named","kind":11,"uri":"file://'"$TMP"'/Drawable.bux","data":"Named","range":{"start":{"line":0,"character":0},"end":{"line":0,"character":5}},"selectionRange":{"start":{"line":0,"character":0},"end":{"line":0,"character":5}}}}}'
|
||||
rpc '{"jsonrpc":"2.0","id":7,"method":"shutdown","params":null}'
|
||||
rpc '{"jsonrpc":"2.0","method":"exit","params":null}'
|
||||
} | "$LSP" 2>/dev/null | tr '\r' '\n' > "$TMP/out.txt"
|
||||
|
||||
if ! grep -q '0.16.0' "$TMP/out.txt"; then
|
||||
echo "WARN: version not 0.16.0"
|
||||
fi
|
||||
|
||||
python3 - <<'PY' "$TMP/out.txt" "$TMP"
|
||||
import json, sys, re
|
||||
raw = open(sys.argv[1]).read()
|
||||
tmp = sys.argv[2]
|
||||
got = {}
|
||||
for p in re.split(r'Content-Length:\s*\d+\s*', raw):
|
||||
p = p.strip()
|
||||
if not p.startswith('{'):
|
||||
continue
|
||||
try:
|
||||
j = json.loads(p)
|
||||
except Exception:
|
||||
continue
|
||||
if 'id' in j and 'result' in j:
|
||||
got[j['id']] = j['result']
|
||||
|
||||
def names(r):
|
||||
if not isinstance(r, list):
|
||||
return set()
|
||||
return {x.get('name') for x in r if isinstance(x, dict)}
|
||||
|
||||
def uris(r):
|
||||
if not isinstance(r, list):
|
||||
return set()
|
||||
return {x.get('uri', '') for x in r if isinstance(x, dict)}
|
||||
|
||||
r2 = got.get(2) or []
|
||||
if 'Drawable' not in names(r2):
|
||||
print('FAIL: prepare Drawable (closed def) missing')
|
||||
print(got.get(2))
|
||||
sys.exit(1)
|
||||
# Prefer closed file URI for the type item
|
||||
u2 = uris(r2)
|
||||
if not any('Drawable.bux' in u for u in u2):
|
||||
print(f'FAIL: prepare Drawable should point at Drawable.bux, got {u2}')
|
||||
sys.exit(1)
|
||||
print(' prepare Drawable → closed Drawable.bux: OK')
|
||||
|
||||
r3 = got.get(3) or []
|
||||
n3 = names(r3)
|
||||
if 'Circle' not in n3 or 'Square' not in n3:
|
||||
print(f'FAIL: subtypes Drawable expected Circle+Square (empty extends), got {n3}')
|
||||
print(json.dumps(r3, indent=2)[:800])
|
||||
sys.exit(1)
|
||||
u3 = uris(r3)
|
||||
if not any('Shapes.bux' in u for u in u3):
|
||||
print(f'FAIL: subtypes should reference closed Shapes.bux, got {u3}')
|
||||
sys.exit(1)
|
||||
print(f' subtypes Drawable → {sorted(n3)} (closed, empty extend): OK')
|
||||
|
||||
r4 = got.get(4) or []
|
||||
if 'Circle' not in names(r4):
|
||||
print('FAIL: prepare Circle missing')
|
||||
print(got.get(4))
|
||||
sys.exit(1)
|
||||
print(' prepare Circle → closed Shapes.bux: OK')
|
||||
|
||||
r5 = got.get(5) or []
|
||||
n5 = names(r5)
|
||||
if 'Drawable' not in n5 or 'Named' not in n5:
|
||||
print(f'FAIL: supertypes Circle expected Drawable+Named, got {n5}')
|
||||
print(json.dumps(r5, indent=2)[:800])
|
||||
sys.exit(1)
|
||||
print(f' supertypes Circle → {sorted(n5)}: OK')
|
||||
|
||||
r6 = got.get(6) or []
|
||||
n6 = names(r6)
|
||||
if n6 != {'Circle'}:
|
||||
print(f'FAIL: subtypes Named expected only Circle, got {n6}')
|
||||
sys.exit(1)
|
||||
print(f' subtypes Named → {sorted(n6)}: OK')
|
||||
|
||||
print('PASS: LSP type hierarchy workspace index (0.16)')
|
||||
PY
|
||||
+86
-1
@@ -166,4 +166,89 @@ if ! echo "$main_body" | grep -vE '#line 1 "' | grep -qE '#line [0-9]+ ".*Main\.
|
||||
fi
|
||||
echo " Expr/Stmt sourceFile: PASS (Main stmts → Main.bux only)"
|
||||
|
||||
echo "PASS: selfhost smoke (move_field + multi-file #line + HirNode/Expr sourceFile)"
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5) Binary op parentheses — C precedence must not rewrite Mul(Add,c)
|
||||
# Without parens selfhost emitted `a + b * c` → 7 instead of (a+b)*c → 9
|
||||
# ---------------------------------------------------------------------------
|
||||
echo "=== selfhost: binary op parentheses (C precedence) ==="
|
||||
PREC="$TMP/c_precedence"
|
||||
mkdir -p "$PREC/src"
|
||||
cp -a "$ROOT/rt" "$PREC/"
|
||||
cat > "$PREC/bux.toml" <<'EOF'
|
||||
[Package]
|
||||
Name = "c_precedence"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
EOF
|
||||
cp "$ROOT/examples/c_precedence.bux" "$PREC/src/Main.bux"
|
||||
|
||||
(cd "$PREC" && "$BUXC2" project .)
|
||||
prec_out=$("$PREC/build/c_precedence")
|
||||
echo "$prec_out" | tee "$TMP/prec.out"
|
||||
grep -q 'PASS c_precedence' "$TMP/prec.out"
|
||||
# Generated C must parenthesize the sum before multiply
|
||||
if ! grep -A3 '^int MulSum' "$PREC/build/main.c" | grep -qE '\(a \+ b\) \* c|\(\(a \+ b\) \* c\)'; then
|
||||
echo "error: MulSum C lacks parentheses around a+b before *c" >&2
|
||||
grep -n -A5 '^int MulSum' "$PREC/build/main.c" | head -20
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -A3 '^int SubDiv' "$PREC/build/main.c" | grep -qE '\(a - b\) / c|\(\(a - b\) / c\)'; then
|
||||
echo "error: SubDiv C lacks parentheses around a-b before /c" >&2
|
||||
grep -n -A5 '^int SubDiv' "$PREC/build/main.c" | head -20
|
||||
exit 1
|
||||
fi
|
||||
echo " binary parens: PASS (run 9/3/6/7 + C has (a + b) * c)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6) declarative macro! / quote! expand (session 60 selfhost parity)
|
||||
# ---------------------------------------------------------------------------
|
||||
echo "=== selfhost: macro! expand ==="
|
||||
MAC="$TMP/macro_twice"
|
||||
mkdir -p "$MAC/src"
|
||||
cp -a "$ROOT/rt" "$MAC/"
|
||||
cat > "$MAC/bux.toml" <<'EOF'
|
||||
[Package]
|
||||
Name = "macro_twice"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
EOF
|
||||
cp "$ROOT/examples/macro_twice.bux" "$MAC/src/Main.bux"
|
||||
(cd "$MAC" && "$BUXC2" project .)
|
||||
mac_out=$("$MAC/build/macro_twice")
|
||||
echo "$mac_out" | tee "$TMP/mac.out"
|
||||
grep -q 'PASS macro_twice' "$TMP/mac.out"
|
||||
grep -q '42' "$TMP/mac.out"
|
||||
echo " macro!: PASS (twice/add2/quote → 42/42/43)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 7) multi-rep / compound zip / nested template $(…)* (session 63)
|
||||
# ---------------------------------------------------------------------------
|
||||
echo "=== selfhost: macro_nested multi-rep ==="
|
||||
MACN="$TMP/macro_nested"
|
||||
mkdir -p "$MACN/src"
|
||||
cp -a "$ROOT/rt" "$MACN/"
|
||||
cat > "$MACN/bux.toml" <<'EOF'
|
||||
[Package]
|
||||
Name = "macro_nested"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
EOF
|
||||
cp "$ROOT/examples/macro_nested.bux" "$MACN/src/Main.bux"
|
||||
(cd "$MACN" && "$BUXC2" project .)
|
||||
macn_out=$("$MACN/build/macro_nested")
|
||||
echo "$macn_out" | tee "$TMP/macn.out"
|
||||
grep -q 'PASS macro_nested' "$TMP/macn.out"
|
||||
grep -q '33' "$TMP/macn.out"
|
||||
grep -q '63' "$TMP/macn.out"
|
||||
echo " macro_nested: PASS (add_pairs/sum_groups/double_each/named_sum)"
|
||||
|
||||
echo "PASS: selfhost smoke (move_field + multi-file #line + HirNode/Expr sourceFile + binop parens + macro! + multi-rep)"
|
||||
|
||||
Reference in New Issue
Block a user