fix: validate protocol impl method signatures vs the protocol declaration (issue 0178)

The issue-0176 conformance gate was name-only, so an impl P for T with a
mismatched return/param type (or arity) built a wrong-ABI thunk that
silently miscompiled (exit 0, wrong value). firstUnimplementedMethod now
validates arity (after self), each param type, and the return type
against the protocol declaration, substituting protocol Self->concrete
via resolveProtoTypeSubSelf (recurses through pointer/many-pointer/
optional/slice/array so []Self<->[]T match; conservative .unresolved for
Self-in-generic-arg). Comparison is by structural formatTypeName
(alias/module/spelling independent); typesClearlyDiffer skips when either
side has an unresolved leaf at any depth, biasing against false-positives.

Regressions: diagnostics/1201 (negative), protocols/0420 (positive,
[]Self param). Verified by 3+3 adversarial reviews (a mid-fix []Self
false-positive was found and closed); suite 792/0.
This commit is contained in:
agra
2026-06-23 08:48:31 +03:00
parent 8b613af96b
commit c41f51aed3
10 changed files with 293 additions and 2 deletions

View File

@@ -0,0 +1,22 @@
// A protocol-method impl with the right NAME but a mismatched RETURN type (or
// parameter type) is a hard error, not a silent miscompile. Regression (issue
// 0178): the issue-0176 conformance gate is name-based, so an `impl P for T`
// whose method returns `bool` where the protocol method returns `i64` passed
// the gate and built a wrong-ABI thunk — dispatch through the erased protocol
// silently returned the wrong value (exit 0, no diagnostic). The conformance
// gate now validates each impl method's signature (arity after `self`, each
// parameter type, and the return type) against the protocol declaration, with
// `Self` substituted to the concrete type (so the REQUIRED `*T` / `T` impl form
// is NOT flagged). A clear type mismatch reports at the erasure site, exit 1.
#import "modules/std.sx";
P :: protocol { val :: (self: *Self) -> i64; }
T :: struct { n: i64 = 7; }
impl P for T { val :: (self: *T) -> bool { return true; } } // return bool != i64
main :: () {
t := T.{ n = 7 };
p : P = t; // <- 'T' does not implement 'P' (return type)
print("{}\n", p.val());
}

View File

@@ -0,0 +1,5 @@
error: 'T' does not implement protocol 'P': method 'val' has a mismatched signature — return type: protocol declares 'i64', impl declares 'bool' (a protocol-method impl must match the protocol's declared types exactly, with `Self` written as `T`)
--> examples/diagnostics/1201-diagnostics-protocol-impl-signature-mismatch.sx:20:3
|
20 | p : P = t; // <- 'T' does not implement 'P' (return type)
| ^^^^^^^^^^

View File

@@ -0,0 +1,33 @@
// `Self` nested inside a SLICE in a protocol-method param resolves
// correctly: the conformance gate substitutes `Self → T` THROUGH the
// `[]` wrapper (and the `*Self` receiver), so a correct `[]T` impl is
// accepted and dispatched — not falsely rejected as `[]Self` ≠ `[]T`.
//
// Regression (issue 0178 adversarial review): the signature gate's
// `Self`-substitution originally only reached the bare leaf and a
// one-level pointer/optional wrapper, so `[]Self` resolved to a real
// slice named `[]Self` and a correct `[]T` impl was WRONGLY flagged.
#import "modules/std.sx";
P :: protocol {
// `*Self` receiver + `[]Self` slice param + `Self` value param.
total :: (self: *Self, xs: []Self, extra: Self) -> i64;
}
T :: struct { n: i64 = 0; }
impl P for T {
total :: (self: *T, xs: []T, extra: T) -> i64 {
sum := self.n + extra.n;
for xs (x) { sum = sum + x.n; }
return sum;
}
}
main :: () {
t := T.{ n = 10 };
p : P = t;
arr : [3]T = .{ T.{ n = 1 }, T.{ n = 2 }, T.{ n = 3 } };
// 10 (self) + 1+2+3 (slice) + 100 (extra) = 116
print("{}\n", p.total(arr[..], T.{ n = 100 }));
}