Skip to content

Fix code generation bugs in refined reference/attribute handling - #144

Closed
ChrisH07 wants to merge 9 commits into
NMFCode:mainfrom
ChrisH07:fix/refines-code-generation
Closed

Fix code generation bugs in refined reference/attribute handling#144
ChrisH07 wants to merge 9 commits into
NMFCode:mainfrom
ChrisH07:fix/refines-code-generation

Conversation

@ChrisH07

@ChrisH07 ChrisH07 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Fixes several bugs in Meta2ClassesTransformation's code generation for refined ("Refines") references and attributes, found while generating code for a metamodel that combines multiple base metamodels through inheritance.

  • False merge conflicts between explicit interface implementations (ClassGenerator.AreConflicting) — the conflict detector ignored PrivateImplementationType, so two properties implementing different interfaces with the same name (e.g. a refined reference's explicit interface implementation) were mistaken for a naming collision and one got silently renamed, breaking the interface implementation.
  • Non-deterministic proxy class naming (Feature2Proxy) — generated proxy nested classes were named only from the feature's short name, so a refined reference collided with its base's proxy. The unsafe rename fallback then mutated a shared/memoized object, making output non-deterministic across runs. Proxy names are now qualified by declaring type.
  • Wrong cast target in SetFeature (Class2Type) — casting this used the refined feature's value type instead of its declaring type, so setting a base reference/attribute by name cast to the wrong interface.
  • Colliding generated collection class names (RefinedReferenceCollectionClassGenerator / RefinedAttributeCollectionClassGenerator) — a refined reference/attribute named "Children" generated a collection class with the same name as the framework's own children-aggregation class, so C# resolved to the wrong one.

Also included, found in the same investigation but unrelated to Refines:

  • Ambiguous System.Type reference (Meta2ClassesTransformation.CreateReferenceForMappedType) — attributes typed with NMF's built-in SystemType primitive generated an unqualified Type reference that collided with NMF.Models.Meta.Type, which is imported into every generated file by default. This is a general bug independent of the others; it just surfaced in the same test model.

Testing:

  • All existing test suites pass (CodeGenerationTests, Transformations.Tests, EcoreInterop.Tests), with golden-reference snapshots updated to reflect the intentional proxy-naming change.
  • Added a new regression test (Refines.ecore / RefinesModelGeneratedSuccessfully). The new model has a class refining both a single-valued reference and a "Children"-named containment collection from an interface base — the same shape that triggered all four Refines bugs above. Verified this test fails (with the original merge-conflict exceptions and/or compile errors) when any one of the four fixes is individually reverted, confirming it's a meaningful regression guard.
  • Additionally verified end-to-end against a real multi-metamodel project that previously failed to compile — it now generates and builds cleanly and deterministically across repeated runs.

Summary by MergeMonkey

  • Corrections:
    • Fixed false merge conflicts when generating code for refined references/attributes with explicit interface implementations
    • Fixed proxy class naming collision when a feature is refined/redefined from base interface
    • Fixed type resolution in SetFeature for refined attributes to use declaring type instead of feature type
    • Fixed System.Type reference to always use full name to avoid ambiguity with NMF.Models.Meta namespace
    • Fixed refined collection class naming collision with regular collection classes
    • Fixed SetFeature to skip refined references/attributes in regular processing to avoid shadowing the refined dispatching case
    • Fixed proxy type reference naming in AddToExpressionForFeature to match Feature2Proxy output
  • Spring Cleaning:
    • Updated CodeGen.history with patch entry for refined references/attributes fix

Chris Hedden added 7 commits July 22, 2026 11:03
AreConflicting compared only member name and CLR type, so two properties
implementing different interfaces with the same name (e.g. a refined
reference redeclared via explicit interface implementation) were treated
as a naming collision. Since properties don't support merging, the
fallback silently renamed one of them, breaking the interface
implementation. Now compares PrivateImplementationType too.
NMF.Models.Meta is imported into every generated file and declares its
own class named Type, so attributes mapped to System.Type generated an
unqualified "Type" reference that was ambiguous between the two.
Feature2Proxy named each generated proxy nested class only from the
feature's short name. A reference that refines/redefines a base feature
keeps the same name, so its proxy collided with the base feature's own
proxy. The merge-conflict fallback then renamed one of the two shared,
memoized CodeTypeDeclaration instances in place, which could corrupt
unrelated classes that referenced the same instance and made output
non-deterministic across runs. Proxy names are now qualified by
declaring type so the collision can't occur; Class2Type's matching
string-based type reference is updated to stay in sync.
Reflects the previous commit's fix that qualifies generated proxy class
names with their declaring type.
SetFeature cast "this" to the type of the refined feature's value
instead of the type that declares it, so setting a base reference or
attribute by name through the generic feature API cast to the wrong
interface and could no longer see the property being set.
…ction

The collection class generated for a refined multi-valued reference or
attribute was named "{scope}{featureName}Collection", which collided
with the unrelated nested "{scope}ChildrenCollection" class the code
generator always creates for a class's containment children whenever a
feature happened to be named "Children". C# resolved the unqualified
name to the wrong (nested) class, producing a container implementation
that didn't implement the interface its property declared.
None of the existing code generation tests exercise Refines: it requires
a class re-declaring a same-named feature from an interface supertype.
Adds a minimal model with a single-valued reference and a
containment collection reference both refined from an interface base,
matching the shape that triggered several recent bugs. Verified this
test fails (merge-conflict exceptions and/or compile errors) when any
of those fixes are individually reverted.
@mergemonkeyhq

mergemonkeyhq Bot commented Jul 22, 2026

Copy link
Copy Markdown
Risk AssessmentNEEDS-TESTING · ~15 min review

Focus areas: ClassGenerator conflict resolution logic · Feature2Proxy naming convention · Class2Type type resolution for refined features · SetFeature refined feature dispatching

Assessment: Bug fixes in code generation logic affecting refined reference/attribute handling with test coverage added.

Walkthrough

This PR fixes several code generation bugs in the Meta2ClassesTransformation system. The primary fix addresses false merge conflicts when generating code for metamodels with inheritance by checking private implementation types. Additionally, proxy class naming was changed to include the declaring type name to prevent collisions when refining/redefining base features. The PR also fixes type resolution bugs in SetFeature and adds a new test case for the Refines metamodel.

Changes

Files Summary
Explicit Interface Implementation Conflict Resolution
Transformations/CodeGen/ClassGenerator.cs
Adds SamePrivateImplementationType check to AreConflicting method for methods, properties, and events to prevent false conflicts between explicit interface implementations targeting different interfaces
Proxy Naming Convention Fix
Transformations/Models.MetaTransformation/Meta/Feature2Proxy.cs
Transformations/Models.MetaTransformation/Meta/Class2Type.cs
Transformations/Tests/CodeGenerationTests/References/railway.cs
Transformations/Tests/CodeGenerationTests/References/Families.cs
Transformations/Tests/CodeGenerationTests/References/Relational.cs
Transformations/Tests/CodeGenerationTests/References/OperationTest.cs
Transformations/Tests/CodeGenerationTests/References/architectureCRA.cs
Transformations/Tests/CodeGenerationTests/References/DefaultValueTest.cs
Transformations/Tests/CodeGenerationTests/References/FromSchemaEcore.cs
Transformations/Tests/CodeGenerationTests/References/NameClashes.cs
Transformations/Tests/CodeGenerationTests/References/Persons.cs
Changes proxy class naming from {FeatureName}Proxy to {DeclaringTypeName}{FeatureName}Proxy to avoid naming collisions when refining/redefining base features. Updates all generated test code to use new naming convention.
Type Reference Full Name Fix
Transformations/Models.MetaTransformation/Meta/Meta2ClassesTransformation.cs
Ensures System.Type always uses FullName to avoid ambiguity with NMF.Models.Meta namespace which also declares a Type class
Refined Collection Naming Fix
Transformations/Models.MetaTransformation/Meta/RefinedAttributeCollectionClassGenerator.cs, RefinedReferenceCollectionClassGenerator.cs
Adds 'Refined' prefix to collection class names to avoid collision with Class2Children nested types when attribute/reference is named 'Children'
SetFeature Refined Feature Dispatching Fix
Transformations/Models.MetaTransformation/Meta/Class2Type.cs
Fixes SetFeature to skip refined references/attributes in regular AddReferencesOfClass/AddAttributesOfClass processing to avoid shadowing the refined dispatching case. Also fixes type resolution to use f.DeclaringType instead of f.Type for proper type casting.
Test Infrastructure Updates
Transformations/Tests/CodeGenerationTests/ModelTests.cs
Transformations/Tests/CodeGenerationTests/CodeGenerationTests.csproj
Transformations/Tests/CodeGenerationTests/Refines.ecore
Transformations/Tests/CodeGenerationTests/References/Refines.cs
Adds new test case for Refines.ecore metamodel and includes generated Refines.cs in project
CodeGen History Update
Transformations/CodeGen.history
Adds patch entry documenting the refined references and attributes code generation fix

Sequence Diagram

sequenceDiagram
    participant M as Metamodel (Ecore)
    participant T as Meta2ClassesTransformation
    participant F2P as Feature2Proxy
    participant C2T as Class2Type
    participant CG as ClassGenerator
    participant G as Generated Code
    
    M->>T: Process Refines.ecore
    T->>F2P: Transform ITypedElement to proxy
    F2P->>F2P: Generate qualified name (DeclaringType + FeatureName + Proxy)
    T->>C2T: Add refined references/attributes
    C2T->>C2T: Use f.DeclaringType for type resolution
    C2T->>C2T: Skip refined features in regular SetFeature processing
    C2T->>CG: Add members to type
    CG->>CG: Check AreConflicting with PrivateImplementationType
    CG-->>T: No conflict (different interfaces)
    T-->>G: Generate valid code
Loading

Dig Deeper With Commands

  • /review <file-path> <function-optional>
  • /chat <file-path> "<question>"
  • /roast <file-path>

Runs only when explicitly triggered.

Comment on lines 1235 to +1239
{
if (feature.UpperBound == 1)
{
var propTypeRef = new CodeTypeReference(feature.Name.ToPascalCase() + "Proxy");
// Must match the name Feature2Proxy assigns to the generated proxy nested type.
var propTypeRef = new CodeTypeReference((feature.Parent as IType).Name.ToPascalCase() + feature.Name.ToPascalCase() + "Proxy");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refined feature generation still emits colliding SetFeature keys

The proxy-name change is correct, but the refined setter path still generates branches keyed only by the original feature name. In the new Refines output that creates two feature == "ITEM" cases in Concrete.SetFeature, so the first branch returns and the refined-base assignment is never reachable.

When emitting refined SetFeature cases, give the refined slot a distinct generated key or merge both assignments into a single branch when they intentionally share the same external feature name.

Was this helpful?

  • 👍 Yes
  • 👎 No

@mergemonkeyhq

mergemonkeyhq Bot commented Jul 22, 2026

Copy link
Copy Markdown

Actionable Comments Posted: 1

🧾 Coverage Summary
✔️ Covered (19 files)
- Transformations/CodeGen/ClassGenerator.cs
- Transformations/Models.MetaTransformation/Meta/Class2Type.cs
- Transformations/Models.MetaTransformation/Meta/Feature2Proxy.cs
- Transformations/Models.MetaTransformation/Meta/Meta2ClassesTransformation.cs
- Transformations/Models.MetaTransformation/Meta/RefinedAttributeCollectionClassGenerator.cs
- Transformations/Models.MetaTransformation/Meta/RefinedReferenceCollectionClassGenerator.cs
- Transformations/Tests/CodeGenerationTests/CodeGenerationTests.csproj
- Transformations/Tests/CodeGenerationTests/ModelTests.cs
- Transformations/Tests/CodeGenerationTests/References/DefaultValueTest.cs
- Transformations/Tests/CodeGenerationTests/References/Families.cs
- Transformations/Tests/CodeGenerationTests/References/FromSchemaEcore.cs
- Transformations/Tests/CodeGenerationTests/References/NameClashes.cs
- Transformations/Tests/CodeGenerationTests/References/OperationTest.cs
- Transformations/Tests/CodeGenerationTests/References/Persons.cs
- Transformations/Tests/CodeGenerationTests/References/Refines.cs
- Transformations/Tests/CodeGenerationTests/References/Relational.cs
- Transformations/Tests/CodeGenerationTests/References/architectureCRA.cs
- Transformations/Tests/CodeGenerationTests/References/railway.cs
- Transformations/Tests/CodeGenerationTests/Refines.ecore

Chris Hedden added 2 commits July 22, 2026 13:22
A reference or attribute that refines a base feature shares its external
name, so SetFeature generated two branches keyed on the same upper-cased
name: one for the feature's own declaration and one for the refined
slot. Since the first always returns, the refined branch - the one that
correctly dispatches to whichever implementation applies - was
unreachable. Skip the feature's own branch when it refines another
feature, since the refined branch already covers it.

Found by MergeMonkey review.
@ChrisH07 ChrisH07 closed this Jul 22, 2026
@ChrisH07
ChrisH07 deleted the fix/refines-code-generation branch July 22, 2026 18:47
@ChrisH07
ChrisH07 restored the fix/refines-code-generation branch July 22, 2026 18:48
@ChrisH07 ChrisH07 reopened this Jul 22, 2026
@ChrisH07

Copy link
Copy Markdown
Contributor Author

Closing in favor of a new PR from a slash-free branch name — this branch's slash breaks the EtiCat prerelease-version-string check in CI. See the replacement PR for the same changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant