aboutsummaryrefslogtreecommitdiff
path: root/tests/drivers/toolchain_lsp_contract_test.js
diff options
context:
space:
mode:
authorGravatar Moon Flower <moonflower@elhenney.local> 2026-10-14 08:29:46 +0330
committerGravatar Moon Flower <moonflower@elhenney.local> 2026-10-14 08:29:46 +0330
commit1c53304d03414890af0da7432d1aea87e754a14a (patch)
tree97190d86cf548c9bc63f68c86a4d2a4f09809713 /tests/drivers/toolchain_lsp_contract_test.js
parenta (diff)
feat: grow memory limits, add icon/actor libraries, overhaul LSP diagnostics
Compiler and VM - make every memory map limit dynamic and adjustable at both build time and runtime, including compiler tables, stacks, and every execution region - reserve and grow tables, operand and return stacks, frame segments, and image-building output without relocating live code or globals - rename i/o and string helpers consistently (puts -> put_s, putn -> put_n, scat -> cat_s) across compiler, runtime, examples, tests, and docs - recast and consume values assigned to globals from inside functions - allow fixed and const together, add typed closure returns (fn:t(...)), multi-include syntax (include rt, run, core;), and recurse type inference - never infer string literals as word, anywhere, as argument, variable, or return type - draft docs/BORROW.md: a borrow checker less intrusive than Rust's but more intelligent LSP and editor integration - require explicit includes and give every toolchain area its own namespace (rt, lsp, run, core) - hover for built-in types, inferred types, values known at hover time, array and pointer lifetime/size, library names in include statements, and inferred lifetimes with explanations - honor /* */ comments as documentation for variables and arguments, and stop misattributing section headers or trailing comments as variable hints - stream diagnostics and cancel stale requests on change - jump to definition and references, including cross-file - quick fixes with automatic library import, and namespace completion Hints and diagnostics - render hints as blue squigglies by default, with hints on/small/off per file - suppress magic-number hints via a preceding /* */ comment, and say so - warn on unused arguments; allow multiple _ discards in parameters, pattern matching, and tagged enums, warning if a _ is actually used - rewrite hint and comment prose in professional compiler-doc register Libraries - add icon.zc: run-time self-modification, find/replace syntax, type and block edits, and step-through inspection of locals and globals - add actor.zc: async actors with Actor<I,O>, implicit this, spawn/recv/send, injected into the compiler machinery at include time rather than built in Tooling and docs - reorganize sources, tests, and designs into subdirectories - unify windows entry points in tinyvm.bat and add a matching tinyvm.sh - add --help across the toolchain - polish the learning guide, stdlib docs, and design notes Tests - add accept, reject, and regression coverage across the compiler, LSP, - runtime, icon, actors, threads, formatting, and the toolchain contracts, plus fuzzers and property tests
Diffstat (limited to 'tests/drivers/toolchain_lsp_contract_test.js')
-rw-r--r--tests/drivers/toolchain_lsp_contract_test.js306
1 files changed, 306 insertions, 0 deletions
diff --git a/tests/drivers/toolchain_lsp_contract_test.js b/tests/drivers/toolchain_lsp_contract_test.js
new file mode 100644
index 0000000..0d1b515
--- /dev/null
+++ b/tests/drivers/toolchain_lsp_contract_test.js
@@ -0,0 +1,306 @@
+// pending requirements have explicit protocol oracles, selectable in small groups.
+const assert = require('assert');
+const cp = require('child_process');
+const fs = require('fs');
+const path = require('path');
+const os = require('os');
+const host = process.argv[2], group = process.env.CONTRACT_GROUP;
+const frame = m => { const b = Buffer.from(JSON.stringify({ jsonrpc: '2.0', ...m })); return Buffer.concat([Buffer.from(`Content-Length: ${b.length}\r\n\r\n`), b]); };
+function run(text, requests = [], uri = 'file:///contracts.zc', files = []) {
+ const messages = [{ id: 1, method: 'initialize', params: {} }, ...files.map(([file, content]) => ({ method: 'zc/file', params: { path: file, text: content } })), { method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, text } } }, ...requests.map((r, i) => {
+ const at = text.indexOf(r.needle) + (r.offset || 0); assert.ok(at >= 0, r.needle);
+ const before = text.slice(0, at);
+ return { id: i + 2, method: r.method || 'textDocument/hover', params: { textDocument: { uri }, ...(r.method === 'textDocument/codeAction' ? {} : { position: { line: before.split('\n').length - 1, character: before.length - before.lastIndexOf('\n') - 1 } }), ...(r.params || {}) } };
+ }), { method: 'exit' }];
+ let output;
+ if (host === '--wasm' || host === '--vm') {
+ const wasm = host === '--wasm';
+ const bridge = require(path.resolve('editors/vscode/client', wasm ? 'wasmproc.js' : 'vmproc.js'));
+ const proc = (wasm ? bridge.wasmProcess : bridge.vmProcess)(path.resolve(process.argv[3]), { log: () => {} });
+ const chunks = []; proc.stdout.on('data', b => chunks.push(b));
+ for (const message of messages) proc.stdin.write(frame(message));
+ output = Buffer.concat(chunks);
+ } else {
+ const r = cp.spawnSync(host, process.argv.slice(3), { input: Buffer.concat(messages.map(frame)), timeout: 60000, maxBuffer: 32 << 20 });
+ assert.strictEqual(r.status, 0, String(r.stderr)); output = r.stdout;
+ }
+ const replies = []; let data = output;
+ while (data.length) {
+ const end = data.indexOf('\r\n\r\n'); assert.ok(end >= 0);
+ const n = Number(/Content-Length:\s*(\d+)/i.exec(data.subarray(0, end).toString())[1]);
+ assert.ok(data.length >= end + 4 + n);
+ replies.push(JSON.parse(data.subarray(end + 4, end + 4 + n))); data = data.subarray(end + 4 + n);
+ }
+ const ds = replies.filter(r => r.method === 'textDocument/publishDiagnostics').at(-1)?.params.diagnostics || [];
+ return { ds, result: id => replies.find(r => r.id === id)?.result };
+}
+let failures = 0, checks = 0;
+function test(g, name, f) {
+ if (group && group !== g) return;
+ checks++;
+ try { f(); console.log('ok ' + g + ': ' + name); }
+ catch (e) { failures++; console.error('FAIL ' + g + ': ' + name + '\n' + e.message); }
+}
+for (const [uri, prefix] of [['file:///contracts.zc', ''], ['tinyvm:/fixture.zc', '']]) {
+ test('hovers', 'word-string global at declaration and use ' + uri, () => {
+ const text = prefix + 'let hello = "hi"; fn main() { return hello[0]; }';
+ const r = run(text, [{ needle: 'hello =' }, { needle: 'hello[0]' }], uri);
+ for (const id of [2, 3]) { const md = r.result(id)?.contents.value || ''; assert.match(md, /\(char\*\) let hello \/\/ global;/); assert.ok(!md.includes('; type ')); }
+ });
+ test('hovers', 'fixed global array at declaration and use ' + uri, () => {
+ const r = run('let prog[20000]; fn main() { return prog[0]; }', [{ needle: 'prog[20000]' }, { needle: 'prog[0]' }], uri);
+ for (const id of [2, 3]) assert.match(r.result(id)?.contents.value || '', /\(word\[20000\]\) let prog \/\/ global;/);
+ });
+}
+test('strings', 'packed declaration, parameter, return and uses keep bstr', () => {
+ const r = run('fn echo(s) { return s; } fn main() { let hello = b"hi"; let other = echo(hello); return other[0]; }', [{ needle: 'hello =' }, { needle: 'hello);' }, { needle: 'other[0]' }, { needle: 'echo(hello)' }, { needle: 'return s;', offset: 7 }]);
+ assert.ok(!r.ds.some(d => d.severity === 1), JSON.stringify(r.ds));
+ for (const id of [2, 3, 4, 6]) { const md = r.result(id)?.contents.value || ''; assert.match(md, /\(bstr\) let/); assert.ok(!/\(word\)/.test(md)); }
+ assert.match(r.result(5)?.contents.value || '', /bstr echo\(bstr s\)/);
+});
+test('strings', 'grouped word-string local stays char*', () => {
+ const r = run('fn main() { let n = 1, text = "hi"; return text[0] + n; }', [{ needle: 'text[0]' }]);
+ assert.match(r.result(2)?.contents.value || '', /\(char\*\) let text/);
+});
+test('strings', 'inferred packed return is shown in hover', () => {
+ const text = 'fn text() { return b"hi"; } fn main() { let result = text(); return result[0]; }';
+ const r = run(text, [{ needle: 'text()' }, { needle: 'result = ' }, { needle: 'result[0]' }]);
+ assert.match(r.result(2)?.contents.value || '', /bstr text\(\)/);
+ for (const id of [3, 4]) assert.match(r.result(id)?.contents.value || '', /\(bstr\) let result/);
+});
+test('strings', 'inferred word-string return is shown in hover', () => {
+ const text = 'fn text() { return "hi"; } fn main() { let result = text(); return result[0]; }';
+ const r = run(text, [{ needle: 'text()' }, { needle: 'result = ' }, { needle: 'result[0]' }]);
+ assert.match(r.result(2)?.contents.value || '', /char\* text\(\)/);
+ for (const id of [3, 4]) assert.match(r.result(id)?.contents.value || '', /\(char\*\) let result/);
+});
+for (const [name, src, expected, severity = 3] of [
+ ['unused parameter', 'fn ignore(int unused) { return 0; }', true],
+ ['used parameter', 'fn use(int used) { return used; }', false],
+ ['explicit discard parameter', 'fn ignore(int _) { return 0; }', false],
+ ['duplicate discard parameters', 'fn ignore(int _, int _) { return 0; }', false],
+ ['write-only parameter', 'fn write(int unused) { unused = 2; return 0; }', true],
+ ['shadowed parameter', 'fn shadow(int unused) { { int unused = 2; return unused; } }', true],
+ ['unused method argument', 'struct point { fn f(int unused) { return 0; } }', true],
+ ['unused closure argument', 'fn main() { let f = fn:int(int unused) { return 0; }; return f(2); }', true],
+ ['unused generic argument', 'T first<T>(T used, T unused) { return used; } fn main() { return first<int>(1, 2); }', true],
+ ['on directive', 'hints on; fn ignore(int unused) { return 0; }', true, 3],
+ ['small directive', 'hints small; fn ignore(int unused) { return 0; }', true, 4],
+ ['off directive', 'hints off; fn ignore(int unused) { return 0; }', false]
+]) test('arguments', name, () => {
+ const r = run(src + '\nfn marker() { return 0; }');
+ const hints = r.ds.filter(d => /unused.*(argument|parameter)|(?:argument|parameter).*unused/.test(d.message));
+ assert.strictEqual(hints.length > 0, expected, JSON.stringify(r.ds));
+ if (expected) { assert.strictEqual(hints[0].severity, severity); assert.strictEqual(src.split('\n')[hints[0].range.start.line].slice(hints[0].range.start.character, hints[0].range.end.character), 'unused'); }
+});
+for (const [name, src, warning] of [
+ ['discard read warns', 'fn f(int _) { return _; }', true],
+ ['discard assignment is not a read', 'fn f(int _) { _ = 2; return 0; }', false],
+ ['duplicate payload discards', 'include rt; enum pair { item { int x; int y; }, other }; fn main() { let p = pair.item(1, 2); switch(p) { case item(_, _): return 0; case _: return 1; } }', false],
+ ['bound payload discard read', 'include rt; enum pair { item { int x; int y; }, other }; fn main() { let p = pair.item(1, 2); switch(p) { case item(_, _): return _; case _: return 1; } }', true],
+ ['numeric catch-all', 'fn f(int n) { switch(n) { case 1: return 1; case _: return 2; } }', false]
+]) test('discards', name, () => {
+ const r = run(src);
+ assert.ok(!r.ds.some(d => d.severity === 1), JSON.stringify(r.ds));
+ const ds = r.ds.filter(d => /discard.*read|read.*discard/.test(d.message));
+ assert.strictEqual(ds.length > 0, warning, JSON.stringify(r.ds));
+ for (const d of ds) { assert.strictEqual(d.severity, 2); assert.strictEqual(src.slice(d.range.start.character, d.range.end.character), '_'); }
+});
+for (const [name, src, count, severity] of [
+ ['uncommented literal', 'fn f(int n) { return n + 0x40ff; }', 1, 3],
+ ['commented literal', 'fn f(int n) { return n + /* offset in memory */ 0x40ff; }', 0, 3],
+ ['empty comment escape', 'fn f(int n) { return n + /**/ 0x40ff; }', 0, 3],
+ ['nearby comment does not hide it', 'fn f(int n) { return /* explanation of n */ n + 0x40ff; }', 1, 3],
+ ['compact dots', 'hints small; fn f(int n) { return n + 0x40ff; }', 1, 4],
+ ['off directive', 'hints off; fn f(int n) { return n + 0x40ff; }', 0, 3]
+]) test('magic', name, () => { const ds = run(src).ds.filter(d => /magic|numeric literal|number .*has no name/i.test(d.message)); assert.strictEqual(ds.length, count, JSON.stringify(ds)); if (count) { assert.strictEqual(ds[0].severity, severity); assert.match(ds[0].message, /inline block comment/); assert.ok(!ds[0].message.includes('a const says what it means')); } });
+test('navigation', 's_to_n points to its runtime declaration', () => {
+ const r = run('include rt; fn main() { return rt.s_to_n(b"42"); }', [{ method: 'textDocument/definition', needle: 's_to_n' }]);
+ const loc = r.result(2)?.[0]; assert.ok(loc, 'missing definition'); assert.strictEqual(loc.uri, 'tinyvm:/rt.zc');
+ const line = fs.readFileSync('src/runtime/rt.zc', 'utf8').split('\n')[loc.range.start.line];
+ assert.strictEqual(line.slice(loc.range.start.character, loc.range.end.character), 's_to_n');
+});
+for (const [module, file] of [['core', 'core.zc'], ['rt', 'rt.zc']]) test('navigation', 'qualified duplicate names open the correct module: ' + module, () => {
+ const source = 'include rt, core; fn main() { return ' + module + '.syscall(9,0,0,0); }';
+ const r = run(source, [{ needle: 'syscall(', method: 'textDocument/definition' }, { needle: 'syscall(' }]);
+ const location = r.result(2)?.[0];
+ assert.strictEqual(location?.uri, 'tinyvm:/' + file);
+ const sourcePath = module === 'core' ? 'src/core/core.zc' : 'src/runtime/rt.zc';
+ const line = fs.readFileSync(sourcePath, 'utf8').split('\n')[location.range.start.line];
+ assert.strictEqual(line.slice(location.range.start.character, location.range.end.character), 'syscall');
+ if (module === 'core') assert.match(r.result(3)?.contents.value || '', /int syscall\(int operation/);
+});
+test('navigation', 'qualified references retain their resolved module identity', () => {
+ const source = 'include rt, core; fn main() { core.syscall(9,0,0,0); rt.syscall(9,0,0,0); return core.syscall(9,0,0,0); }';
+ const r = run(source, [{ needle: 'core.syscall', offset: 5, method: 'textDocument/references', params: { context: { includeDeclaration: false } } }]);
+ const references = r.result(2) || [];
+ assert.strictEqual(references.length, 2);
+ for (const location of references) assert.ok(source.slice(location.range.start.character, location.range.end.character).includes('syscall'));
+});
+test('modules', 'qualified completion exports only imported module', () => {
+ const r = run('include rt; fn main() { rt.put_', [{ method: 'textDocument/completion', needle: 'rt.put_', offset: 7 }]);
+ const items = r.result(2)?.items || r.result(2) || [];
+ assert.ok(items.some(i => i.label === 'put_s')); assert.ok(!items.some(i => i.label === 'evals'));
+});
+test('modules', 'lsp exports require an explicit include and qualified name', () => {
+ const accepted = run('include lsp; fn main() { return lsp.hid(97); }');
+ assert.ok(!accepted.ds.some(d => d.severity === 1), JSON.stringify(accepted.ds));
+ const missing = run('fn main() { return lsp.hid(97); }');
+ assert.ok(missing.ds.some(d => d.severity === 1 && /include lsp/.test(d.message)), JSON.stringify(missing.ds));
+ const bare = run('include lsp; fn main() { return hid(97); }');
+ assert.ok(bare.ds.some(d => d.severity === 1), JSON.stringify(bare.ds));
+});
+test('modules', 'missing include reports error at qualified member', () => {
+ const r = run('fn main() { rt.put_s("hi"); return 0; }');
+ assert.ok(r.ds.some(d => d.severity === 1 && /include rt/.test(d.message)), JSON.stringify(r.ds));
+});
+for (const [name, description] of [['rt', /runtime/], ['run', /resident compiler/], ['core', /vm host/], ['lsp', /language server/], ['std', /standard library/], ['icon', /syntax transformations/]]) {
+ test('modulehover', 'module documentation in comma-separated include: ' + name, () => {
+ const text = 'include rt, run, core, lsp, std, icon; fn main() { return 0; }';
+ const r = run(text, [{ needle: name + (name === 'icon' ? ';' : ',') }]);
+ const hover = r.result(2)?.contents;
+ assert.strictEqual(hover?.kind, 'markdown');
+ assert.match(hover.value, new RegExp('```zc\\ninclude ' + name + ';\\n```'));
+ assert.match(hover.value, description);
+ assert.match(hover.value, new RegExp('`' + name + '\\.[^`]+`'));
+ });
+}
+test('modulehover', 'a local with the same name retains its variable hover', () => {
+ const r = run('fn main() { int rt = 3; return rt; }', [{ needle: 'rt;' }]);
+ assert.match(r.result(2)?.contents.value || '', /\(int\) let rt/);
+ assert.ok(!r.result(2).contents.value.includes('include rt;'));
+});
+test('modulehover', 'comments and newlines preserve the include list', () => {
+ const r = run('include /* runtime */ rt,\n /* compiler */ run; fn main() { return 0; }', [{ needle: 'run;' }]);
+ assert.match(r.result(2)?.contents.value || '', /include run;[\s\S]*resident compiler/);
+});
+for (const [name, member] of [['rt', 'put_s'], ['run', 'evals'], ['core', 'syscall'], ['lsp', 'hid'], ['std', 'array_map'], ['icon', 'find_replace']]) {
+ test('namespacecompletion', 'qualified members, signatures and documentation: ' + name, () => {
+ const r = run('include rt, run, core, lsp, std, icon; fn main() { ' + name + '.', [{ needle: name + '.', offset: name.length + 1, method: 'textDocument/completion' }]);
+ const items = r.result(2)?.items || [];
+ const item = items.find(i => i.label === member);
+ assert.ok(item, JSON.stringify(items.map(i => i.label)));
+ assert.ok(item.detail && item.documentation?.value, JSON.stringify(item));
+ assert.ok(!items.some(i => i.label === 'main'));
+ assert.strictEqual(new Set(items.map(i => i.label)).size, items.length);
+ });
+}
+function applyWorkspaceEdit(text, edit, uri) {
+ const edits = edit.documentChanges?.flatMap(c => c.textDocument.uri === uri ? c.edits : []) || edit.changes?.[uri] || [];
+ const offset = p => text.split('\n').slice(0, p.line).reduce((n, l) => n + l.length + 1, 0) + p.character;
+ for (const e of edits.sort((a, b) => offset(b.range.start) - offset(a.range.start))) text = text.slice(0, offset(e.range.start)) + e.newText + text.slice(offset(e.range.end));
+ return text;
+}
+for (const [name, source, needle] of [
+ ['qualified runtime import', 'fn main() { rt.put_n(7); }', 'put_n'],
+ ['qualified compiler import', 'fn main() { run.evals("return 0;"); }', 'evals'],
+ ['qualified host import', 'fn main() { return core.syscall(9,0,0,0); }', 'syscall'],
+ ['bare runtime call needs qualification too', 'fn main() { put_n(7); }', 'put_n'],
+ ['imported module still needs a qualified name', 'include rt; fn main() { put_n(7); }', 'put_n'],
+ ['namespace and header are preserved', '// file overview\nnamespace demo;\n\n// prints the result\nfn main() { rt.put_n(7); }', 'put_n'],
+ ['crlf stays crlf', 'namespace demo;\r\nfn main() { rt.put_n(7); }\r\n', 'put_n'],
+]) test('quickfix', name, () => {
+ const uri = 'file:///contracts.zc', before = source.slice(0, source.indexOf(needle));
+ const position = { line: before.split('\n').length - 1, character: before.length - before.lastIndexOf('\n') - 1 };
+ const r = run(source, [{ needle, method: 'textDocument/codeAction', params: { range: { start: position, end: position }, context: { diagnostics: [] } } }], uri);
+ assert.ok(r.result(1)?.capabilities.codeActionProvider);
+ const action = r.result(2)?.find(a => a.kind === 'quickfix' && a.edit);
+ assert.ok(action, JSON.stringify(r.result(2)));
+ const changed = applyWorkspaceEdit(source, action.edit, uri);
+ assert.notStrictEqual(changed, source);
+ if (source.includes('\r\n')) assert.ok(!/(?<!\r)\n/.test(changed));
+ if (source.includes('// prints the result')) assert.ok(changed.includes('// prints the result\nfn main()'));
+ if (source.startsWith('include rt;')) assert.strictEqual((changed.match(/include rt;/g) || []).length, 1);
+ const checked = run(changed);
+ assert.ok(!checked.ds.some(d => d.severity === 1), JSON.stringify({ changed, ds: checked.ds }));
+});
+for (const source of ['include rt; fn main() { rt.put_n(7); }', 'fn main() { int put_n = 7; return put_n; }', 'fn main() { return unknown_name; }']) test('quickfix', 'no unrelated import edits: ' + source, () => {
+ const needle = source.includes('put_n') ? 'put_n' : 'unknown_name';
+ const r = run(source, [{ needle, method: 'textDocument/codeAction' }]);
+ assert.deepStrictEqual(r.result(2), []);
+});
+test('qualifiers', 'fixed const array hover preserves both qualifiers and shape', () => {
+ const r = run('fn main() { fixed const int[2] data = {1,2}; return data[0]; }', [{ needle: 'data[0]' }]);
+ assert.ok(!r.ds.some(d => d.severity === 1), JSON.stringify(r.ds));
+ assert.match(r.result(2)?.contents.value || '', /\(const int\[2\]\) let data/);
+});
+test('qualifiers', 'const fixed global hover preserves its type', () => {
+ const r = run('const fixed int[2] data = {1,2}; fn main() { return data[0]; }', [{ needle: 'data[0]' }]);
+ assert.match(r.result(2)?.contents.value || '', /\(const int\[2\]\) let data \/\/ global;/);
+});
+test('qualifiers', 'readonly qualifiers reject aliasing without optional type checking', () => {
+ const r = run('typecheck off; fn mutate(int* p) { p[0] = 9; } fn main() { fixed const int[2] data = {1,2}; mutate(data); }');
+ assert.ok(r.ds.some(d => d.severity === 1 && /const storage cannot be converted/.test(d.message)), JSON.stringify(r.ds));
+});
+test('inference', 'typed callee constrains caller arguments and returned values', () => {
+ const source = 'int seed(int cap) { return cap + 1; } fn squares(n) { let l = seed(n); return l; } fn main() { return squares(3); }';
+ const r = run(source, [{ needle: 'squares(n)' }, { needle: 'seed(n)', offset: 5 }, { needle: 'return l;', offset: 7 }]);
+ assert.match(r.result(2)?.contents.value || '', /int squares\(int n\)/);
+ assert.match(r.result(3)?.contents.value || '', /\(int\) let n/);
+ assert.match(r.result(4)?.contents.value || '', /\(int\) let l/);
+});
+test('inference', 'twelve caller layers converge in reverse declaration order', () => {
+ let source = '';
+ for (let i = 12; i > 0; i--) source += `fn layer${i}(n) { return layer${i - 1}(n); }\n`;
+ source += 'int layer0(int n) { return n + 1; } fn main() { return layer12(3); }';
+ const r = run(source, [{ needle: 'layer12(n)' }, { needle: 'layer7(n)' }, { needle: 'layer1(n)' }]);
+ for (const [id, name] of [[2, 'layer12'], [3, 'layer7'], [4, 'layer1']]) assert.match(r.result(id)?.contents.value || '', new RegExp('int ' + name + '\\(int n\\)'));
+});
+test('inference', 'recursive calls retain an anchored integer return and argument type', () => {
+ const source = 'int seed(int n) { return n; } fn recursive(n) { if (n <= 0) return seed(n); return recursive(n - 1); } fn main() { return recursive(3); }';
+ const r = run(source, [{ needle: 'recursive(n)' }]);
+ assert.match(r.result(2)?.contents.value || '', /int recursive\(int n\)/);
+});
+test('inference', 'uncalled caller parameters inherit typed callee requirements', () => {
+ const r = run('int seed(int n) { return n; } fn wrapper(n) { return seed(n); }', [{ needle: 'wrapper(n)' }]);
+ assert.match(r.result(2)?.contents.value || '', /int wrapper\(int n\)/);
+});
+test('inference', 'conflicting caller types do not become a single inferred signature', () => {
+ const r = run('fn identity(value) { return value; } fn main() { identity(3); return identity("text")[0]; }', [{ needle: 'identity(value)' }]);
+ assert.match(r.result(2)?.contents.value || '', /fn identity\(value\)/);
+});
+test('inference', 'unknown reassignment invalidates the previous initializer flow', () => {
+ const r = run('int seed(int n) { return n; } fn external(); fn changed(n) { let result = seed(n); result = external(); return result; }', [{ needle: 'changed(n)' }]);
+ assert.ok(!/int changed/.test(r.result(2)?.contents.value || ''), JSON.stringify(r.result(2)));
+});
+test('inference', 'owned list allocation constrains capacity and caller types', () => {
+ const text = 'include rt; .u mklist(cap) { let l = rt.new(cap + 1); l[0] = 0; return l; } .u squares(n) { let l = mklist(n); return l; }';
+ const r = run(text, [{ needle: 'mklist(cap)' }, { needle: 'squares(n)' }, { needle: 'mklist(n)', offset: 7 }]);
+ assert.ok(!r.ds.some(d => d.severity === 1), JSON.stringify(r.ds));
+ assert.match(r.result(2)?.contents.value || '', /word\.u mklist\(int cap\)/);
+ assert.match(r.result(3)?.contents.value || '', /word\.u squares\(int n\)/);
+ assert.match(r.result(4)?.contents.value || '', /\(int\) let n/);
+});
+test('inference', 'array indices do not constrain the type of an element result', () => {
+ const r = run('include rt; fn lookup(index) { let data = rt.new(4); return data[index]; } fn main() { return lookup(1); }', [{ needle: 'lookup(index)' }]);
+ assert.ok(!/int lookup/.test(r.result(2)?.contents.value || ''), JSON.stringify(r.result(2)));
+});
+test('inference', 'typed fields do not constrain the containing parameter to a scalar', () => {
+ const r = run('struct point { int x; } fn coordinate(p) { return p.x; }', [{ needle: 'coordinate(p)' }]);
+ assert.ok(!/coordinate\(int p\)/.test(r.result(2)?.contents.value || ''), JSON.stringify(r.result(2)));
+});
+test('quickfix', 'an entire-line request finds the compiler error inside the range', () => {
+ const source = 'fn main() { rt.put_n(7); }';
+ const r = run(source, [{ needle: 'fn', method: 'textDocument/codeAction', params: { range: { start: { line: 0, character: 0 }, end: { line: 0, character: source.length } }, context: { diagnostics: [{ range: { start: { line: 99, character: 99 }, end: { line: 99, character: 99 } }, message: 'stale' }] } } }]);
+ assert.ok(r.result(2)?.some(a => a.title === 'include rt;'), JSON.stringify(r.result(2)));
+});
+test('quickfix', 'an owner-name cursor can import a qualified module', () => {
+ const source = 'fn main() { rt.put_n(7); }', start = { line: 0, character: source.indexOf('rt.') };
+ const r = run(source, [{ needle: 'rt.', method: 'textDocument/codeAction', params: { range: { start, end: start }, context: { diagnostics: [] } } }]);
+ assert.ok(r.result(2)?.some(a => a.title === 'include rt;'));
+});
+test('modulehover', 'source library overview is available without a built-in module entry', () => {
+ const folder = fs.mkdtempSync(path.join(os.tmpdir(), 'zc-module-hover-'));
+ try {
+ fs.mkdirSync(path.join(folder, 'src/lib'), { recursive: true });
+ fs.writeFileSync(path.join(folder, 'src/lib/widget.zc'), 'namespace widget;\n\n// converts widget coordinates into grid positions.\n\nint position(int x) { return x; }\n');
+ const r = run('include widget; fn main() { return widget.position(3); }', [{ needle: 'widget;' }], 'file://' + path.join(folder, 'main.zc'), [[path.join(folder, 'src/lib/widget.zc'), fs.readFileSync(path.join(folder, 'src/lib/widget.zc'), 'utf8')]]);
+ assert.ok(!r.ds.some(d => d.severity === 1), JSON.stringify(r.ds));
+ assert.match(r.result(2)?.contents.value || '', /converts widget coordinates into grid positions/);
+ fs.writeFileSync(path.join(folder, 'src/lib/widget.zc'), 'namespace widget;\n\n/** converts grid positions into widget coordinates. */\n\nint position(int x) { return x; }\n');
+ const block = run('include widget; fn main() { return widget.position(3); }', [{ needle: 'widget;' }], 'file://' + path.join(folder, 'main.zc'), [[path.join(folder, 'src/lib/widget.zc'), fs.readFileSync(path.join(folder, 'src/lib/widget.zc'), 'utf8')]]);
+ assert.match(block.result(2)?.contents.value || '', /converts grid positions into widget coordinates/);
+ } finally { fs.rmSync(folder, { recursive: true, force: true }); }
+});
+assert.ok(checks, 'no selected protocol contracts: ' + group);
+process.exitCode = failures ? 1 : 0;