|
@@ -407,6 +407,11 @@ export function isAssignable(to: M2Type, from: M2Type): boolean {
|
|
|
return normKey(t) === normKey(f);
|
|
return normKey(t) === normKey(f);
|
|
|
}
|
|
}
|
|
|
if (t.kind === 'pointer' && f.kind === 'nil') return true;
|
|
if (t.kind === 'pointer' && f.kind === 'nil') return true;
|
|
|
|
|
+ // gm2 lets ADDRESS take NIL and mix with pointers in both directions
|
|
|
|
|
+ // (`q := NIL`, `p := q`, `q := p` all compile with -fiso).
|
|
|
|
|
+ if (t.kind === 'whole' && t.id === 'ADDRESS' && f.kind === 'nil') return true;
|
|
|
|
|
+ if (t.kind === 'pointer' && f.kind === 'whole' && f.id === 'ADDRESS') return true;
|
|
|
|
|
+ if (t.kind === 'whole' && t.id === 'ADDRESS' && f.kind === 'pointer') return true;
|
|
|
if (t.kind === 'pointer' && f.kind === 'pointer') return typeKey(t.target) === typeKey(f.target);
|
|
if (t.kind === 'pointer' && f.kind === 'pointer') return typeKey(t.target) === typeKey(f.target);
|
|
|
if (t.kind === 'proc' && f.kind === 'nil') return true;
|
|
if (t.kind === 'proc' && f.kind === 'nil') return true;
|
|
|
if (t.kind === 'proc' && f.kind === 'proc') return true;
|
|
if (t.kind === 'proc' && f.kind === 'proc') return true;
|
|
@@ -424,11 +429,24 @@ export function isAssignable(to: M2Type, from: M2Type): boolean {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/** Open-array formal accepts a fixed array with compatible elements. */
|
|
|
|
|
|
|
+/** Open-array formal accepts a fixed array with compatible elements.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Calibrated against gm2 (`-fiso`): an open `ARRAY OF LOC`/`BYTE`/`WORD`
|
|
|
|
|
+ * is a raw-memory formal and additionally takes any actual (e.g.
|
|
|
|
|
+ * `RawIO.Read` into a `CHAR` variable); other element types still
|
|
|
|
|
+ * require an array actual. The element may be unresolved — gm2's own
|
|
|
|
|
+ * `SYSTEM.def` keeps those types internal, so `SYSTEM.LOC` often stays
|
|
|
|
|
+ * a qualified name — hence the name-based fallback. */
|
|
|
|
|
+const OCTET_ELEMENTS = new Set(['LOC', 'BYTE', 'WORD']);
|
|
|
export function isArrayArgCompatible(param: M2Type, arg: M2Type): boolean {
|
|
export function isArrayArgCompatible(param: M2Type, arg: M2Type): boolean {
|
|
|
if (param.kind === 'array' && param.size === null && arg.kind === 'array') {
|
|
if (param.kind === 'array' && param.size === null && arg.kind === 'array') {
|
|
|
return isAssignable(param.element, arg.element);
|
|
return isAssignable(param.element, arg.element);
|
|
|
}
|
|
}
|
|
|
|
|
+ if (param.kind === 'array' && param.size === null && arg.kind !== 'unknown') {
|
|
|
|
|
+ const el = param.element.kind === 'subrange' ? param.element.base : param.element;
|
|
|
|
|
+ if (el.kind === 'whole' && OCTET_ELEMENTS.has(el.id)) return true;
|
|
|
|
|
+ if (el.kind === 'named' && OCTET_ELEMENTS.has(el.name)) return true;
|
|
|
|
|
+ }
|
|
|
return isAssignable(param, arg);
|
|
return isAssignable(param, arg);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -465,8 +483,11 @@ export function isComparable(a: M2Type, b: M2Type, ordering: boolean): boolean {
|
|
|
if (ordering) return false;
|
|
if (ordering) return false;
|
|
|
return typeKey(ta.target) === typeKey(tb.target);
|
|
return typeKey(ta.target) === typeKey(tb.target);
|
|
|
}
|
|
}
|
|
|
- if (ta.kind === 'pointer' && tb.kind === 'nil') return !ordering;
|
|
|
|
|
- if (tb.kind === 'pointer' && ta.kind === 'nil') return !ordering;
|
|
|
|
|
|
|
+ // ADDRESS compares like a pointer (probed: `q = NIL` compiles).
|
|
|
|
|
+ const addrLike = (t: M2Type): boolean =>
|
|
|
|
|
+ t.kind === 'pointer' || (t.kind === 'whole' && t.id === 'ADDRESS');
|
|
|
|
|
+ if (addrLike(ta) && tb.kind === 'nil') return !ordering;
|
|
|
|
|
+ if (addrLike(tb) && ta.kind === 'nil') return !ordering;
|
|
|
// Arrays and records are never comparable (confirmed against gm2).
|
|
// Arrays and records are never comparable (confirmed against gm2).
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
@@ -665,14 +686,17 @@ export function findFieldInType(
|
|
|
}
|
|
}
|
|
|
activeFieldLookups.add(key);
|
|
activeFieldLookups.add(key);
|
|
|
try {
|
|
try {
|
|
|
- const dot = t.name.lastIndexOf('.');
|
|
|
|
|
- const qualifier = dot >= 0 ? t.name.slice(0, dot) : null;
|
|
|
|
|
- const typeName = dot >= 0 ? t.name.slice(dot + 1) : t.name;
|
|
|
|
|
- const r = resolveName(hostUnit, hostDir, line, ch, qualifier, typeName);
|
|
|
|
|
- if (!r || r.sym.kind !== 'type') return null;
|
|
|
|
|
|
|
+ // Preferred: the declaration identity stamped at resolution time.
|
|
|
|
|
+ // It reaches types that are not visible under their own name — e.g.
|
|
|
|
|
+ // `tm` behind an imported `ptrToTM = POINTER TO tm` (foreign `libc`).
|
|
|
|
|
+ const viaDecl = t.decl ? findTypeByDecl(hostUnit, t.decl) : null;
|
|
|
|
|
+ const r = viaDecl ?? resolveByTypeName(hostUnit, hostDir, line, ch, t.name);
|
|
|
|
|
+ if (!r) return null;
|
|
|
const declUnit = r.filePath === hostUnit.filePath ? hostUnit : unitOfFile(r.filePath);
|
|
const declUnit = r.filePath === hostUnit.filePath ? hostUnit : unitOfFile(r.filePath);
|
|
|
if (!declUnit) return null;
|
|
if (!declUnit) return null;
|
|
|
- const f = r.sym.children.find(c => c.name === fieldName);
|
|
|
|
|
|
|
+ const typeSym = viaDecl ? viaDecl.sym : r.sym.kind === 'type' ? r.sym : null;
|
|
|
|
|
+ if (!typeSym) return null;
|
|
|
|
|
+ const f = typeSym.children.find(c => c.name === fieldName);
|
|
|
if (!f) return null;
|
|
if (!f) return null;
|
|
|
return {
|
|
return {
|
|
|
sym: f, unit: declUnit,
|
|
sym: f, unit: declUnit,
|
|
@@ -684,6 +708,43 @@ export function findFieldInType(
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/** Locate a type symbol through a stamped declaration identity
|
|
|
|
|
+ * (`filePath:line:ch` as built by `attachName`). */
|
|
|
|
|
+function findTypeByDecl(
|
|
|
|
|
+ hostUnit: M2Unit, decl: string,
|
|
|
|
|
+): { sym: M2Symbol; filePath: string } | null {
|
|
|
|
|
+ const m = /^(.*):(\d+):(\d+)$/.exec(decl);
|
|
|
|
|
+ if (!m) return null;
|
|
|
|
|
+ const [, fp, ls, cs] = m;
|
|
|
|
|
+ const line = Number(ls);
|
|
|
|
|
+ const ch = Number(cs);
|
|
|
|
|
+ const unit = fp === hostUnit.filePath ? hostUnit : unitOfFile(fp);
|
|
|
|
|
+ if (!unit) return null;
|
|
|
|
|
+ const sym = findSymbolAt(unit.symbols, line, ch);
|
|
|
|
|
+ if (!sym || sym.kind !== 'type') return null;
|
|
|
|
|
+ return { sym, filePath: fp };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function findSymbolAt(syms: M2Symbol[], line: number, ch: number): M2Symbol | null {
|
|
|
|
|
+ for (const s of syms) {
|
|
|
|
|
+ if (s.nameRange.startLine === line && s.nameRange.startCh === ch) return s;
|
|
|
|
|
+ const inner = findSymbolAt(s.children, line, ch);
|
|
|
|
|
+ if (inner) return inner;
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resolveByTypeName(
|
|
|
|
|
+ hostUnit: M2Unit, hostDir: string, line: number, ch: number, name: string,
|
|
|
|
|
+): { sym: M2Symbol; filePath: string } | null {
|
|
|
|
|
+ const dot = name.lastIndexOf('.');
|
|
|
|
|
+ const qualifier = dot >= 0 ? name.slice(0, dot) : null;
|
|
|
|
|
+ const typeName = dot >= 0 ? name.slice(dot + 1) : name;
|
|
|
|
|
+ const r = resolveName(hostUnit, hostDir, line, ch, qualifier, typeName);
|
|
|
|
|
+ if (!r || r.sym.kind !== 'type') return null;
|
|
|
|
|
+ return r;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/** Resolve an imported qualified name (`M.X`) to its declaration for typing. */
|
|
/** Resolve an imported qualified name (`M.X`) to its declaration for typing. */
|
|
|
export function resolveQualified(
|
|
export function resolveQualified(
|
|
|
unit: M2Unit, docDir: string, module: string, name: string,
|
|
unit: M2Unit, docDir: string, module: string, name: string,
|