소스 검색

fix the two generics template/proc-param limitations (+ an imported-proc value bug)

1. Arithmetic/relations over a type formal: the expression checks
   (ArithCheck/RelCheck/BoolCheck/OrdCheck/InCheck/EqCheck) now skip on
   Resolve(x) = InvalidType, matching Assignable. An unbound formal
   resolves to InvalidType, so it is treated as unknown in the template
   and checked normally when bound in an instance. (Sum over T works.)
2. Proc-typed formals in a DEF/IMPL pair: FormalOk compares two FProc
   formals structurally (ProcTypesOk), like open arrays, instead of by
   TypeIndex. No more false 235.
3. LowerProcAddr falls back to SymBase for a qualified/imported proc
   name, so an imported procedure used as a value resolves to its code
   address (was 0).

Showcase23 now exercises all three (Sum, ForEach + imported visitor);
new test TImportProc. Suite 219/219; fixpoint OK; corpus unchanged.
Eric Streit 10 시간 전
부모
커밋
65f331e60d

+ 3 - 1
compiler/run_tests.sh

@@ -309,7 +309,9 @@ expect_run_files WrapProg 42 \
 expect_run_files_out Showcase23 35 "list: 5 10 20" \
   ../stdlib/sysio.def ../stdlib/sysio.mod \
   showcase23lib.def showcase23lib.mod \
-  showcase23int.def showcase23int.mod showcase23.mod
+  showcase23int.def showcase23int.mod \
+  showcase23vis.def showcase23vis.mod showcase23.mod
+expect_run_files TImportProc 7 pvi.def pvi.mod t_importproc.mod
 expect_run_files_out Showcase22 42 "showcase22 total=42" \
   ../runtime/syslib/SysShim.def ../runtime/syslib/SysShim.mod \
   ../stdlib/iochan.def ../stdlib/iochan.mod \

+ 11 - 2
compiler/src/Lower.mod

@@ -1529,8 +1529,17 @@ PROCEDURE LowerProcAddr (nm: ARRAY OF CHAR; VAR q: QbeGen.QVal);
    variable): $<base>_<uid>, or the plain C link for external procs. *)
   VAR uid, depth, np: CARDINAL; res: SymTab.TypeIndex; ext: BOOLEAN;
       mg: QbeGen.QVal;
-  BEGIN
-    IF ProcInfo(nm, uid, res, depth, ext, np) THEN
+      base: SymTab.Name; ok: BOOLEAN;
+  BEGIN
+    ok := ProcInfo(nm, uid, res, depth, ext, np);
+    IF NOT ok THEN
+      (* a qualified/imported procedure appears under its base symbol
+         name in the proc table *)
+      IF SymTab.SymBase(nm, base) THEN
+        ok := ProcInfo(base, uid, res, depth, ext, np)
+      END
+    END;
+    IF ok THEN
       IF ext THEN QbeGen.CopyOp(nm, mg) ELSE QbeGen.Mangled(nm, uid, mg) END;
       QbeGen.ProcAddr(mg, q)
     ELSE QbeGen.CopyOp("0", q)

+ 12 - 6
compiler/src/SymTab.mod

@@ -390,6 +390,12 @@ PROCEDURE FormalOk (a, b: TypeIndex): BOOLEAN;
     IF IsOpenArray(a) AND IsOpenArray(b) THEN
       RETURN SameType(ArrayElem(a), ArrayElem(b))
     END;
+    (* Procedure types are built fresh per parse, so a
+       DEFINITION/IMPLEMENTATION pair never shares the index; compare
+       structurally (same result and parameter types). *)
+    IF (tform[Resolve(a)] = FProc) AND (tform[Resolve(b)] = FProc) THEN
+      RETURN ProcTypesOk(Resolve(a), Resolve(b))
+    END;
     RETURN StrCompat(a, b)
   END FormalOk;
 
@@ -2806,7 +2812,7 @@ PROCEDURE ArithCheck (l, r: TypeIndex; divmod: BOOLEAN;
                       VAR res: TypeIndex): BOOLEAN;
   BEGIN
     res := InvalidType;
-    IF (l = InvalidType) OR (r = InvalidType) THEN RETURN TRUE END;
+    IF (Resolve(l) = InvalidType) OR (Resolve(r) = InvalidType) THEN RETURN TRUE END;
     IF (IsLongFamily(l) AND IsLongFamily(r))
        OR (IsLongFamily(l) AND IsIntFamily(r))
        OR (IsIntFamily(l) AND IsLongFamily(r)) THEN
@@ -2823,7 +2829,7 @@ PROCEDURE ArithCheck (l, r: TypeIndex; divmod: BOOLEAN;
 
 PROCEDURE BoolCheck (t: TypeIndex): BOOLEAN;
   BEGIN
-    IF t = InvalidType THEN RETURN TRUE END;
+    IF Resolve(t) = InvalidType THEN RETURN TRUE END;
     RETURN ClassOf(t) = ClBool
   END BoolCheck;
 
@@ -2869,7 +2875,7 @@ PROCEDURE StrCompat (l, r: TypeIndex): BOOLEAN;
 PROCEDURE EqCheck (l, r: TypeIndex): BOOLEAN;
   VAR rl, rr: TypeIndex;
   BEGIN
-    IF (l = InvalidType) OR (r = InvalidType) THEN RETURN TRUE END;
+    IF (Resolve(l) = InvalidType) OR (Resolve(r) = InvalidType) THEN RETURN TRUE END;
     IF ClassOf(l) = ClNil THEN
       RETURN (ClassOf(r) = ClNil) OR (ClassOf(r) = ClPtr)
     END;
@@ -2915,7 +2921,7 @@ PROCEDURE EqCheck (l, r: TypeIndex): BOOLEAN;
 
 PROCEDURE OrdCheck (l, r: TypeIndex): BOOLEAN;
   BEGIN
-    IF (l = InvalidType) OR (r = InvalidType) THEN RETURN TRUE END;
+    IF (Resolve(l) = InvalidType) OR (Resolve(r) = InvalidType) THEN RETURN TRUE END;
     IF IsIntFamily(l) AND IsIntFamily(r) THEN RETURN TRUE END;
     IF (ClassOf(l) = ClLong) AND (ClassOf(r) = ClLong) THEN
       RETURN TRUE
@@ -2945,7 +2951,7 @@ PROCEDURE SetElemClassOk (c: INTEGER): BOOLEAN;
 PROCEDURE InCheck (l, set: TypeIndex): BOOLEAN;
   VAR rs, b: TypeIndex;
   BEGIN
-    IF (l = InvalidType) OR (set = InvalidType) THEN RETURN TRUE END;
+    IF (Resolve(l) = InvalidType) OR (Resolve(set) = InvalidType) THEN RETURN TRUE END;
     rs := Resolve(set);
     IF (rs = InvalidType) OR (tform[rs] # FSet) THEN RETURN FALSE END;
     b := tref[rs];
@@ -2966,7 +2972,7 @@ PROCEDURE InCheck (l, set: TypeIndex): BOOLEAN;
 
 PROCEDURE RelCheck (l, r: TypeIndex; op: INTEGER): BOOLEAN;
   BEGIN
-    IF (l = InvalidType) OR (r = InvalidType) THEN RETURN TRUE END;
+    IF (Resolve(l) = InvalidType) OR (Resolve(r) = InvalidType) THEN RETURN TRUE END;
     IF op = OpIn THEN RETURN InCheck(l, r) END;
     IF (op = OpEq) OR (op = OpNeq1) OR (op = OpNeq2) THEN
       RETURN EqCheck(l, r)

+ 3 - 0
compiler/tests/pvi.def

@@ -0,0 +1,3 @@
+DEFINITION MODULE Pvi;
+PROCEDURE Add(VAR a : INTEGER; v : INTEGER);
+END Pvi.

+ 4 - 0
compiler/tests/pvi.mod

@@ -0,0 +1,4 @@
+IMPLEMENTATION MODULE Pvi;
+PROCEDURE Add(VAR a : INTEGER; v : INTEGER);
+BEGIN a := a + v END Add;
+END Pvi.

+ 11 - 9
compiler/tests/showcase23.mod

@@ -1,9 +1,10 @@
 MODULE Showcase23;
 (* Showcase: a generic doubly linked list.  DList(T) is instantiated as
-   IntList = DList(INTEGER); the program builds a list, walks it with
-   Get(i), reverses it and sums it.  Prints, then exits with the sum. *)
+   IntList = DList(INTEGER).  Exercises three things: a type parameter,
+   arithmetic over it (Sum), and a procedure parameter (ForEach with an
+   imported visitor).  Prints, then exits with the sum. *)
 IMPORT SysIO, IntList;
-VAR l : IntList.List; i, sum, ExitCode : INTEGER;
+VAR l : IntList.List; i, sum, acc, ExitCode : INTEGER;
 
 PROCEDURE Show(pfx : ARRAY OF CHAR);
 BEGIN
@@ -24,14 +25,15 @@ BEGIN
   IntList.Prepend(l, 5);
   Show("list:");
 
+  sum := IntList.Sum(l);
+  SysIO.Write("sum="); SysIO.WriteInt(sum); SysIO.WriteLn;
+
+  acc := 0;
+  IntList.ForEach(l, Visitor.Add, acc);
+  SysIO.Write("foreach="); SysIO.WriteInt(acc); SysIO.WriteLn;
+
   IntList.Reverse(l);
   Show("reversed:");
 
-  sum := 0; i := 0;
-  WHILE i < IntList.Count(l) DO
-    sum := sum + IntList.Get(l, i);
-    i := i + 1
-  END;
-  SysIO.Write("sum="); SysIO.WriteInt(sum); SysIO.WriteLn;
   ExitCode := sum                       (* 35 *)
 END Showcase23.

+ 5 - 2
compiler/tests/showcase23lib.def

@@ -1,4 +1,5 @@
 GENERIC DEFINITION MODULE DList (T : TYPE);
+(* A generic doubly linked list. *)
 TYPE
   Node = POINTER TO NodeRec;
   NodeRec = RECORD
@@ -14,7 +15,9 @@ PROCEDURE Append(VAR l : List; v : T);
 PROCEDURE Prepend(VAR l : List; v : T);
 PROCEDURE Count(VAR l : List) : INTEGER;
 PROCEDURE Get(VAR l : List; i : INTEGER) : T;
-PROCEDURE First(VAR l : List) : T;
-PROCEDURE Last(VAR l : List) : T;
+PROCEDURE Sum(VAR l : List) : T;          (* arithmetic over the formal T *)
 PROCEDURE Reverse(VAR l : List);
+PROCEDURE ForEach(VAR l : List;           (* a procedure parameter *)
+                  visit : PROCEDURE(VAR acc : INTEGER; v : T);
+                  VAR acc : INTEGER);
 END DList.

+ 16 - 5
compiler/tests/showcase23lib.mod

@@ -29,11 +29,13 @@ BEGIN
   RETURN n^.val
 END Get;
 
-PROCEDURE First(VAR l : List) : T;
-BEGIN RETURN l.head^.val END First;
-
-PROCEDURE Last(VAR l : List) : T;
-BEGIN RETURN l.tail^.val END Last;
+PROCEDURE Sum(VAR l : List) : T;
+VAR n : Node; s : T;
+BEGIN
+  s := 0; n := l.head;
+  WHILE n # NIL DO s := s + n^.val; n := n^.next END;
+  RETURN s
+END Sum;
 
 PROCEDURE Reverse(VAR l : List);
 VAR n, nx : Node;
@@ -44,4 +46,13 @@ BEGIN
   END;
   n := l.head; l.head := l.tail; l.tail := n
 END Reverse;
+
+PROCEDURE ForEach(VAR l : List;
+                  visit : PROCEDURE(VAR acc : INTEGER; v : T);
+                  VAR acc : INTEGER);
+VAR n : Node;
+BEGIN
+  n := l.head;
+  WHILE n # NIL DO visit(acc, n^.val); n := n^.next END
+END ForEach;
 END DList.

+ 3 - 0
compiler/tests/showcase23vis.def

@@ -0,0 +1,3 @@
+DEFINITION MODULE Visitor;
+PROCEDURE Add(VAR acc : INTEGER; v : INTEGER);
+END Visitor.

+ 4 - 0
compiler/tests/showcase23vis.mod

@@ -0,0 +1,4 @@
+IMPLEMENTATION MODULE Visitor;
+PROCEDURE Add(VAR acc : INTEGER; v : INTEGER);
+BEGIN acc := acc + v END Add;
+END Visitor.

+ 14 - 0
compiler/tests/t_importproc.mod

@@ -0,0 +1,14 @@
+MODULE TImportProc;
+// An imported procedure used as a value (assigned to a procedure-typed
+// variable and called indirectly) must resolve to its code address.
+// Exit 7.
+IMPORT Pvi;
+TYPE F = PROCEDURE(VAR a : INTEGER; v : INTEGER);
+VAR acc, ExitCode : INTEGER; f : F;
+BEGIN
+  acc := 0;
+  f := Pvi.Add;
+  f(acc, 3);
+  f(acc, 4);
+  ExitCode := acc                       (* 7 *)
+END TImportProc.

+ 65 - 0
docs/summary_generics-limitations-fixed-2026-10-16.md

@@ -0,0 +1,65 @@
+# Generics — the two limitations, fixed (2026-10-16)
+
+Branch **`resolve-check`**, tree clean.  Suite **219/219**; fixpoint
+**OK (3,714,376 bytes)**; corpus **372 OK / 0 crash**.
+
+## 1. Arithmetic (and relations) over a type formal
+
+`Sum` over `T` (`s : T; s := s + n^.val`) was `211` at *template* parse:
+an unbound formal is an unresolved alias, and `ClassOf` maps such an
+alias to `ClPtr` (the opaque-type rule), so `ArithCheck` saw a non-numeric
+operand.
+
+**Fix:** an unbound type formal already `Resolve`s to `InvalidType`; the
+expression checks simply weren't consulting `Resolve`.  `ArithCheck`,
+`RelCheck`, `BoolCheck`, `OrdCheck`, `InCheck` and `EqCheck` now skip on
+`Resolve(x) = InvalidType` (instead of only `x = InvalidType`), which is
+exactly what `Assignable` already did.  So a formal — and, consistently,
+any unresolved type — is treated as *unknown* and checks are skipped;
+instances (where the formal is bound to the actual) are checked normally.
+
+## 2. Procedure-typed formals in a DEFINITION/IMPLEMENTATION pair
+
+A formal typed by an inline `PROCEDURE(...)` tripped a **false `235`**
+("implementation does not match definition"): each parse builds a fresh
+proc type, so the def's and impl's proc type have different `TypeIndex`
+and the signature snapshot compared them by index.
+
+**Fix:** `FormalOk` now compares two `FProc` formals **structurally**
+(same result and parameter types, via `ProcTypesOk`) — the same
+treatment open-array formals already had (fresh descriptors never share
+an index).
+
+## 3. Bonus: an imported procedure used as a value
+
+Verifying #2 surfaced a third bug: `f := Visitor.Add` (an *imported*
+proc, assigned to a proc-typed variable) stored **0** — `LowerProcAddr`
+looked the name up in the proc table under the qualified/alias key.
+**Fix:** fall back to `SymTab.SymBase(nm)` (the base symbol) before
+giving up.  (`t_importproc`.)
+
+## Showcase now exercises all three
+
+`showcase23lib` gained `Sum` (arithmetic over `T`) and
+`ForEach(visit : PROCEDURE(VAR acc : INTEGER; v : T); …)` (a procedure
+parameter), and the program passes the imported `Visitor.Add`:
+
+```
+list: 5 10 20
+sum=35          (Sum over T)
+foreach=35      (ForEach + imported visitor procedure)
+reversed: 20 10 5
+```
+
+Files: `showcase23lib.def/.mod`, `showcase23int.def/.mod`,
+`showcase23vis.def/.mod`, `showcase23.mod`.
+
+## Verify
+
+```sh
+cd .../m2compiler-V3/compiler
+./M2 ../stdlib/sysio.def ../stdlib/sysio.mod \
+    tests/showcase23lib.def tests/showcase23lib.mod \
+    tests/showcase23int.def tests/showcase23int.mod \
+    tests/showcase23vis.def tests/showcase23vis.mod tests/showcase23.mod
+```