fix(gnovm): embedded type identity — struct field name + interface method-set flatten#5739
fix(gnovm): embedded type identity — struct field name + interface method-set flatten#5739ltzmaxwell wants to merge 42 commits into
Conversation
🛠 PR Checks SummaryAll Automated Checks passed. ✅ Manual Checks (for Reviewers):
Read More🤖 This bot helps streamline PR reviews by verifying automated checks and providing guidance for contributors and reviewers. ✅ Automated Checks (for Contributors):🟢 Maintainers must be able to edit this pull request (more info) ☑️ Contributor Actions:
☑️ Reviewer Actions:
📚 Resources:Debug
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
davd-gzl
left a comment
There was a problem hiding this comment.
Verified on 155f1a7 against a side-by-side Go run and current master:
struct{ Int } interface{ SAlias }
vs struct{ int } vs interface{ Stringer }
Go distinct ✓ identical ✓
master (pre) identical ✗ identical ✓
PR 155f1a7 distinct ✓ distinct ✗ <- new divergence
…elling
Interface identity is the method set, so interface{ SAlias } must equal interface{ Stringer }; only struct embeds take the written name. Adds alias5 regression test. interface{ Stringer } != interface{ Str()string } stays (pre-existing; needs method-set flattening, see TODO).
Replaces gnolang#5739's minimal interface-embed fix: instead of naming embeds from the resolved type, flatten embedded interfaces into their method set at construction, so the embed name leaves the TypeID entirely. interface{Stringer} == interface{Str()string} now holds too, not just alias-vs-target. Consensus-breaking (anonymous-interface TypeIDs change); land with a chain upgrade.
…tening The name fillEmbeddedName set on embeds was discarded by flattenInterfaceMethods anyway; embed=false drops the dead work and matches the runtime path.
…branches The embedded-interface branches in FindEmbeddedFieldType/VerifyImplementedBy only fire for interfaces decoded from persisted state, since new ones are flattened.
…attening interfaces Flattening hoisted an embedded interface's unexported method into the enclosing interface, which re-qualified it with the wrong package — over-collapsing distinct interface types and letting a local type satisfy another package's sealed interface. Record each method's origin package (new FieldType.PkgPath) and use it for TypeID qualification and satisfaction gating. Adds ADR + cross-package regression filetests. FieldType gains an amino field (empty-guarded in binary, so non-flattened state is unchanged); land with a chain upgrade.
Covers exported vs unexported pkgpath stamping, cross-package origin preservation, diamond dedup, same-name-different-package coexistence, and the conflict panic.
… assume flattened Records the sequencing: keep the FindEmbeddedFieldType/VerifyImplementedBy recursion until a chain-upgrade migration re-flattens persisted interface types, then drop it (assume-flattened); never assume-flattened before the migration. Also notes the conflict-panic follow-up.
… deferred All interfaces are flattened unless decoded from pre-flattening bytes. Fresh chain or post-migration => assume-flattened, drop the recursion; in-place upgrade without migration => keep it. Which applies depends on the rollout model (TBD); PR ships with the recursion retained, safe under either.
…s OK, embed-vs-explicit diverges) Master names the embed from the resolved type and never flattens, so alias-vs-target was already identical on master; only embed-vs-explicit diverged. The alias case broke transiently at 155f1a7 (spelling-based naming). No naming policy fixes embed-vs-explicit — only flattening does.
Why flatten is the correct fix — verified on masterMaster names an embedded-interface entry from the resolved type (
No naming policy — resolved or spelled — can make |
Fixes the make-generate / gno fmt CI check, which requires a blank line between the top-level func and const declarations.
…egory)
alias5 (interface{SAlias}==interface{Stringer}) was an interface-flatten test misfiled in the struct embed-name alias* series; it duplicates iface_embed_id's alias-vs-target case. Remove it and fix alias6's reference. Now alias* = struct embed-name rule, iface_embed_* = interface flattening.
… coexist
interface{ ifaceext.Sec; ifaceext2.Sec } keeps both p.sec and q.sec as distinct methods (verified against Go), stable identity, distinct from embedding one — pins the (pkgpath,name) dedup-coexistence.
iface_embed_conflict.gno: embedding two interfaces with method M of different signatures is rejected like Go's duplicate-method error — a positioned // Error: from the VM (flattenInterfaceMethods panic wrapped as PreprocessError) and a // TypeCheckError: from go/types. ADR: the conflict path needs no follow-up (already a matchable positioned error).
…only cross-pkg Remove unused FieldTypeList.Len (Less/Swap already gone). Extract FieldType.originPkg(fallback), shared by idName and VerifyImplementedBy. flattenInterfaceMethods now stamps PkgPath only for cross-package unexported methods; same-package ones stay unstamped (idName fallback), so slow and fast paths produce identical FieldTypes.
…age, not enclosing interface
…g persisted state
…eep TypeID choke ungated
|
880040d (+ c8c7329) resolves the ADR's deferred rollout decision: the runtime now assumes flattened interfaces — the legacy embedded-interface branches in
Details: |
…(fillType); interior sites debugAssert
…reaches the runtime op)
| // (unsupported state); reject at this decode boundary, which | ||
| // sees every stored type. See panicUnflattened. | ||
| if ct.Methods[i].Type.Kind() == InterfaceKind { | ||
| ct.panicUnflattened(ct.Methods[i]) |
There was a problem hiding this comment.
This is the one ungated legacy-state check, placed here deliberately: store bytes are external input, and fillType sees every stored type — type entries via GetTypeSafe, and object-carried anonymous types via fillTypesOfValue (only DeclaredType collapses to RefType on persist; unnamed types nest inline, so a GetTypeSafe-only check would miss them). An embed entry can only come from bytes persisted before flattening, so it fails at decode, before any semantics run.
The interior sites (FindEmbeddedFieldType / VerifyImplementedBy / TypeID) assume the invariant on a validated store and only assert under -tags debugAssert.
|
@thehowl — several changes landed after your approval; a quick second look would be appreciated. Summary:
|
type Int = int; struct{ Int }→Int, notint), derived from the source AST expr across all three construction paths; covers qualified, pointer, interface,rune/byte, and alias-to-defined-type embeds.flattenInterfaceMethods), so interface identity is the method set, not the embed/alias spelling:interface{ SAlias } == interface{ Stringer } == interface{ S() string }, matching Go.FieldType.PkgPath(new wire field): sealed interfaces stay sealed through flattening, and unexported selection/satisfaction gate on the origin package, not the enclosing interface's.gnovm/adr/pr5739_interface_method_set_flattening.md).alias2–alias6+iface_embed_*filetests (identity, cross-package sealing, access gate, conflict rejection), flatten/TypeID/hard-error unit tests, codec parity for the new wire field; all verified against Go.