Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Transformations/CodeGen.history
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ component NMF-CodeGen.nuspec from CodeGen/Properties/AssemblyInfo.cs
patch (2.0.457): Start using EtiCat
patch: Add support for .NET 10
patch: improve code generation for child elements
patch: update system dependencies
patch: update system dependencies
patch: fix code generation for refined references and attributes
28 changes: 28 additions & 0 deletions Transformations/CodeGen/ClassGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,8 @@ private bool AreConflicting(CodeTypeMember member, CodeTypeMember current)

if (member is CodeMemberMethod memberMethod && current is CodeMemberMethod currentMethod)
{
if (!SamePrivateImplementationType(memberMethod.PrivateImplementationType, currentMethod.PrivateImplementationType)) return false;

if (memberMethod.Parameters.Count != currentMethod.Parameters.Count) return false;
if (memberMethod.TypeParameters.Count != currentMethod.TypeParameters.Count) return false;

Expand All @@ -377,9 +379,35 @@ private bool AreConflicting(CodeTypeMember member, CodeTypeMember current)
if (memberMethod.Parameters[i].Type.BaseType != currentMethod.Parameters[i].Type.BaseType) return false;
}
}

if (member is CodeMemberProperty memberProperty && current is CodeMemberProperty currentProperty)
{
if (!SamePrivateImplementationType(memberProperty.PrivateImplementationType, currentProperty.PrivateImplementationType)) return false;
}

if (member is CodeMemberEvent memberEvent && current is CodeMemberEvent currentEvent)
{
if (!SamePrivateImplementationType(memberEvent.PrivateImplementationType, currentEvent.PrivateImplementationType)) return false;
}

return true;
}

/// <summary>
/// Determines whether two (possibly absent) private implementation types refer to the same interface
/// </summary>
/// <remarks>
/// Explicit interface implementations may legitimately share a member name (e.g. a redefined/refined
/// reference implemented for two different base interfaces), so members must only be considered
/// conflicting when they target the same private implementation type.
/// </remarks>
private static bool SamePrivateImplementationType(CodeTypeReference left, CodeTypeReference right)
{
if (left == null && right == null) return true;
if (left == null || right == null) return false;
return left.BaseType == right.BaseType;
}

/// <summary>
/// Generates the interface members for the given type
/// </summary>
Expand Down
15 changes: 10 additions & 5 deletions Transformations/Models.MetaTransformation/Meta/Class2Type.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1032,17 +1032,21 @@ protected virtual CodeMemberMethod CreateSetFeature(IClass input, CodeTypeDeclar
setFeature.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "feature"));
setFeature.Parameters.Add(new CodeParameterDeclarationExpression(typeof(object), "value"));
var thisRef = new CodeThisReferenceExpression();
AddReferencesOfClass(input, generatedType, (m, f, p, _) => AddSetFeature(m, f, p, context, true, thisRef), setFeature, false, context);
AddAttributesOfClass(input, generatedType, (m, f, p, _) => AddSetFeature(m, f, p, context, false, thisRef), setFeature, context);
// References/attributes that themselves refine a base feature share its external name with the
// case AddRefinedReferencesOfClass/AddRefinedAttributesOfClass generates below. Skip them here so
// that the refined case - which correctly dispatches to whichever implementation applies - is the
// one that is actually reached, instead of being shadowed by this simpler, non-dispatching case.
AddReferencesOfClass(input, generatedType, (m, f, p, _) => f.Refines != null ? m : AddSetFeature(m, f, p, context, true, thisRef), setFeature, false, context);
AddAttributesOfClass(input, generatedType, (m, f, p, _) => f.Refines != null ? m : AddSetFeature(m, f, p, context, false, thisRef), setFeature, context);
var type2Type = Rule<Type2Type>();
AddRefinedReferencesOfClass(input, generatedType, (m, f, p, _) =>
{
var type = context.Trace.ResolveIn(type2Type, f.Type);
var type = context.Trace.ResolveIn(type2Type, f.DeclaringType);
return AddSetFeature(m, f, p, context, true, new CodeCastExpression(type.GetReferenceForType(), thisRef));
}, setFeature, false, context);
AddRefinedAttributesOfClass(input, generatedType, (m, f, p, _) =>
{
var type = context.Trace.ResolveIn(type2Type, f.Type);
var type = context.Trace.ResolveIn(type2Type, f.DeclaringType);
return AddSetFeature(m, f, p, context, false, new CodeCastExpression(type.GetReferenceForType(), thisRef));
}, setFeature, context);
if (setFeature.Statements.Count == 0)
Expand Down Expand Up @@ -1235,7 +1239,8 @@ private static CodeMemberMethod AddToExpressionForFeature(CodeMemberMethod metho
{
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");
Comment on lines 1239 to +1243

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

var ifStmt = new CodeConditionStatement(new CodeBinaryOperatorExpression(new CodeVariableReferenceExpression(parameterName),
CodeBinaryOperatorType.ValueEquality, new CodePrimitiveExpression(property.Name.ToUpperInvariant())));
CodeExpression proxyExpression = new CodeObjectCreateExpression(propTypeRef, new CodeThisReferenceExpression());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ public class Feature2Proxy : TransformationRule<ITypedElement, CodeTypeDeclarati
/// <inheritdoc />
public override void Transform(ITypedElement feature, CodeTypeDeclaration generatedType, ITransformationContext context)
{
generatedType.Name = feature.Name.ToPascalCase() + "Proxy";
var declaringType = context.Trace.ResolveIn(Rule<Type2Type>(), feature.Parent as IType).GetReferenceForType();

// Qualified with the declaring type's name: a reference that refines/redefines a base feature
// keeps the same feature name, so an unqualified name would collide with the base feature's own proxy.
// Class2Type.AddToExpressionForFeature constructs this same name independently and must stay in sync.
generatedType.Name = (feature.Parent as IType).Name.ToPascalCase() + feature.Name.ToPascalCase() + "Proxy";
generatedType.Attributes = MemberAttributes.Private | MemberAttributes.Final;
generatedType.TypeAttributes = System.Reflection.TypeAttributes.NestedPrivate | System.Reflection.TypeAttributes.Sealed;
generatedType.WriteDocumentation(string.Format("Represents a proxy to represent an incremental access to the {0} property", feature.Name));
Expand All @@ -31,7 +36,6 @@ public override void Transform(ITypedElement feature, CodeTypeDeclaration genera
type = new CodeTypeReference(typeof(System.Nullable<>).Name, type);
}

var declaringType = context.Trace.ResolveIn(Rule<Type2Type>(), feature.Parent as IType).GetReferenceForType();
generatedType.BaseTypes.Add(new CodeTypeReference("ModelPropertyChange", declaringType, type));

var modelElementRef = new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), "ModelElement");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,10 @@ private static CodeTypeReference CreateReferenceForDeclaration(bool implementati
private static CodeTypeReference CreateReferenceForMappedType(bool implementation, MappedType mappedType)
{
var reference = new CodeTypeReference();
if (mappedType.SystemType.IsValueType || mappedType.SystemType == typeof(string))
if (mappedType.SystemType.IsValueType || mappedType.SystemType == typeof(string) || mappedType.SystemType == typeof(System.Type))
{
// System.Type must always be fully qualified: NMF.Models.Meta (a namespace imported into every
// generated file) also declares a class named "Type", so a bare "Type" reference is ambiguous.
reference.BaseType = mappedType.SystemType.FullName;
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ public class RefinedAttributeCollectionClassGenerator : TransformationRule<IClas
/// <returns>The uninitialized code type declaration</returns>
public override CodeTypeDeclaration CreateOutput(IClass scope, IAttribute attribute, ITransformationContext context)
{
return CodeDomHelper.CreateTypeDeclarationWithReference(scope.Name.ToPascalCase() + attribute.Name.ToPascalCase() + "Collection", false);
// "Refined" disambiguates this from Class2Children's own nested "{scope}ChildrenCollection" type,
// which it would otherwise collide with whenever a metamodel names an attribute "Children".
return CodeDomHelper.CreateTypeDeclarationWithReference(scope.Name.ToPascalCase() + "Refined" + attribute.Name.ToPascalCase() + "Collection", false);
}

private readonly CodeFieldReferenceExpression parentRef = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "_parent");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public class RefinedReferenceCollectionClassGenerator : TransformationRule<IClas
/// <returns>The newly created code type declaration</returns>
public override CodeTypeDeclaration CreateOutput(IClass scope, IReference reference, ITransformationContext context)
{
return CodeDomHelper.CreateTypeDeclarationWithReference(scope.Name.ToPascalCase() + reference.Name.ToPascalCase() + "Collection", false);
// "Refined" disambiguates this from Class2Children's own nested "{scope}ChildrenCollection" type,
// which it would otherwise collide with whenever a metamodel names a reference "Children".
return CodeDomHelper.CreateTypeDeclarationWithReference(scope.Name.ToPascalCase() + "Refined" + reference.Name.ToPascalCase() + "Collection", false);
}

private readonly CodeFieldReferenceExpression parentRef = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "_parent");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
<Compile Update="References\railway.cs">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Compile>
<Compile Update="References\Refines.cs">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Compile>
<Compile Update="References\Relational.cs">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Compile>
Expand Down Expand Up @@ -130,6 +133,9 @@
<None Update="Persons.ecore">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Refines.ecore">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="railway.ecore">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
Expand Down
6 changes: 6 additions & 0 deletions Transformations/Tests/CodeGenerationTests/ModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ public void NameClashesResolvedSuccessfully()
GenerateAndAssertEcore("NameClashes.ecore");
}

[TestMethod]
public void RefinesModelGeneratedSuccessfully()
{
GenerateAndAssertEcore("Refines.ecore");
}

[TestMethod]
public void DefaultValueExampleGeneratesAndInstanceCanBeLoaded()
{
Expand Down
Loading
Loading