Skip to content
Merged
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
9 changes: 9 additions & 0 deletions .github/workflows/CodeQuality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ jobs:
build:
name: Build
runs-on: ubuntu-latest
env:
# Use cached CRL data only for cert revocation checks. Works around
# NU3012 ("certificate revoked") for transitive ReactiveUI / Splat 19.3.1
# packages whose author certificate (Glenn Watson) was revoked at the CA
# in 2026-Q2. `signatureValidationMode=accept` in NuGet.Config does NOT
# cover this case because it only relaxes the "require signature" check,
# not the chain-validation step that NU3012 fires from. Revert this env
# var once the affected packages are re-signed and re-published.
NUGET_CERT_REVOCATION_MODE: offline
steps:
- uses: actions/checkout@v6.0.2
with:
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ jobs:

runs-on: ubuntu-latest

env:
# Use cached CRL data only for cert revocation checks. Works around
# NU3012 ("certificate revoked") for transitive ReactiveUI / Splat 19.3.1
# packages whose author certificate was revoked at the CA in 2026-Q2.
# Revert this env var once the affected packages are re-signed.
NUGET_CERT_REVOCATION_MODE: offline

steps:
- name: Checkout repository
uses: actions/checkout@v6.0.2
Expand Down
2 changes: 1 addition & 1 deletion Nuget.Config
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<clear />
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
10 changes: 10 additions & 0 deletions SysML2.NET.CodeGenerator/HandleBarHelpers/RuleGenerationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ public TextualNotationRule FindRule(string ruleName)
/// </summary>
public string PendingCursorMove { get; set; }

/// <summary>
/// Monotonically-incrementing counter used to produce unique narrowed-type-check pattern
/// variable names (e.g. <c>owningMembership0</c>, <c>owningMembership1</c>) across the
/// emission of a single rule body. Required because C# pattern-matching variables share
/// the enclosing method scope, so emitting more than one
/// <c>is IOwningMembership owningMembership</c> in distinct guards of the same generated
/// method would collide (CS0136). Incremented by the narrowed-type-check emitter.
/// </summary>
public int NarrowedTypeCheckCounter { get; set; }

/// <summary>
/// Determines whether the next sibling element is a terminal that uses <c>AppendLine</c>
/// (e.g., <c>{</c>, <c>}</c>, <c>;</c>), in which case a trailing space would be unnecessary.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ private void EmitCollectionNonTerminalLoop(EncodedTextWriter writer, IClass umlC
cursorVariableName = cursorDefinition.CursorVariableName;
}

var perItemCall = this.ResolveBuilderCall(umlClass, nonTerminalElement, typeTarget, ruleGenerationContext);
var perItemCall = ResolveBuilderCall(umlClass, nonTerminalElement, typeTarget, ruleGenerationContext);

var whileTypeExclusion = this.ResolveCollectionWhileTypeCondition(cursorVariableName, umlClass, referencedRule, ruleGenerationContext);
var whileTypeExclusion = ResolveCollectionWhileTypeCondition(cursorVariableName, umlClass, referencedRule, ruleGenerationContext);

string whileCondition;

Expand All @@ -84,23 +84,23 @@ private void EmitCollectionNonTerminalLoop(EncodedTextWriter writer, IClass umlC
}
else
{
var allElements = referencedRule?.Alternatives.SelectMany(alt => alt.Elements).ToList();
var allElements = referencedRule.Alternatives.SelectMany(alt => alt.Elements).ToList();

var hasNonAssignmentElements = allElements?.Any(element =>
element is NonTerminalElement or GroupElement) == true;
var hasNonAssignmentElements = allElements.Any(element =>
element is NonTerminalElement or GroupElement);

List<string> assignmentTargetTypes = null;

if (!hasNonAssignmentElements)
{
assignmentTargetTypes = allElements?
assignmentTargetTypes = allElements
.OfType<AssignmentElement>()
.Where(assignmentElement => assignmentElement.Operator == "+=" && assignmentElement.Value is NonTerminalElement)
.Select(assignmentElement =>
{
var valueNonTerminal = (NonTerminalElement)assignmentElement.Value;
var refRule = ruleGenerationContext.FindRule(valueNonTerminal.Name);
var targetName = refRule != null ? refRule.EffectiveTarget : null;
var targetName = refRule?.EffectiveTarget;

if (targetName != null)
{
Expand Down Expand Up @@ -165,14 +165,14 @@ private void EmitCollectionNonTerminalLoop(EncodedTextWriter writer, IClass umlC

var handCodedRuleName = nonTerminalElement.TextualNotationRule?.RuleName ?? nonTerminalElement.Name;

this.EmitHandCodedFallback(writer, handCodedRuleName, ruleGenerationContext, true);
EmitHandCodedFallback(writer, handCodedRuleName, ruleGenerationContext, true);
writer.WriteSafeString(Environment.NewLine);
}

/// <summary>
/// Resolves the type condition for a collection while loop.
/// </summary>
private string ResolveCollectionWhileTypeCondition(string cursorVariableName, IClass umlClass, TextualNotationRule collectionRule, RuleGenerationContext ruleGenerationContext)
private static string ResolveCollectionWhileTypeCondition(string cursorVariableName, IClass umlClass, TextualNotationRule collectionRule, RuleGenerationContext ruleGenerationContext)
{
var siblings = ruleGenerationContext.CurrentSiblingElements;
var currentIndex = ruleGenerationContext.CurrentElementIndex;
Expand Down Expand Up @@ -347,7 +347,7 @@ private string ResolveContentTypeGuard(string cursorVariableName, TextualNotatio
/// <summary>
/// Resolves the builder method call string for a non-terminal element.
/// </summary>
private string ResolveBuilderCall(IClass umlClass, NonTerminalElement nonTerminalElement, string typeTarget, RuleGenerationContext ruleGenerationContext)
private static string ResolveBuilderCall(IClass umlClass, NonTerminalElement nonTerminalElement, string typeTarget, RuleGenerationContext ruleGenerationContext)
{
if (typeTarget == ruleGenerationContext.NamedElementToGenerate.Name)
{
Expand Down Expand Up @@ -386,7 +386,7 @@ private string ResolveBuilderCall(IClass umlClass, NonTerminalElement nonTermina
/// <param name="ruleGenerationContext">The current <see cref="RuleGenerationContext" /></param>
/// <param name="variableName">The variable name from which the property is accessed (typically <c>poco</c>)</param>
/// <returns>The condition expression, or <c>null</c> when no property names are referenced</returns>
private string GenerateInlineOptionalCondition(EncodedTextWriter writer, TextualNotationRule referencedRule, IClass targetClass, RuleGenerationContext ruleGenerationContext, string variableName)
private static string GenerateInlineOptionalCondition(EncodedTextWriter writer, TextualNotationRule referencedRule, IClass targetClass, RuleGenerationContext ruleGenerationContext, string variableName)
{
var propertyNames = referencedRule.QueryAllReferencedPropertyNames(ruleGenerationContext.AllRules);

Expand Down Expand Up @@ -469,35 +469,70 @@ private static string TryBuildCursorTypedCheck(EncodedTextWriter writer, Textual
return null;
}

var resolvedTypeNames = new List<string>();
// Each entry: (WrapperType, InnerType). InnerType is non-null when the assignment's
// referenced rule is a "thin owning wrapper" (target=OwningMembership wrapping a
// single ownedRelatedElement += T); the inner type T then provides the narrowing
// discriminator that distinguishes the wrapper from sibling OwningMembership
// subtypes (e.g. EndFeatureMembership) that share the cursor's collection.
var resolvedTypes = new List<(string Wrapper, string Inner)>();

foreach (var assignmentElement in collectionAssignments)
{
var typeName = ResolveAssignmentTargetTypeName(assignmentElement, targetClass, ruleGenerationContext);
var wrapperType = ResolveAssignmentTargetTypeName(assignmentElement, targetClass, ruleGenerationContext);

if (typeName == null)
if (wrapperType == null)
{
return null;
}

if (!resolvedTypeNames.Contains(typeName))
var innerType = TryResolveWrappedInnerTypeName(assignmentElement, targetClass, ruleGenerationContext);
var entry = (Wrapper: wrapperType, Inner: innerType);

if (!resolvedTypes.Contains(entry))
{
resolvedTypeNames.Add(typeName);
resolvedTypes.Add(entry);
}
}

var cursorVariableName = EnsureCursorDeclared(writer, property, ruleGenerationContext);

if (resolvedTypeNames.Count == 1)
if (resolvedTypes.Count == 1)
{
return $"{cursorVariableName}.Current is {resolvedTypeNames[0]}";
return BuildTypeCheck(cursorVariableName, resolvedTypes[0].Wrapper, resolvedTypes[0].Inner, ruleGenerationContext);
}

var typeChecks = resolvedTypeNames.Select(typeName => $"{cursorVariableName}.Current is {typeName}");
var typeChecks = resolvedTypes.Select(entry => BuildTypeCheck(cursorVariableName, entry.Wrapper, entry.Inner, ruleGenerationContext));

return $"({string.Join(" || ", typeChecks)})";
}

/// <summary>
/// Builds a single cursor-typed boolean check. When <paramref name="innerType"/> is
/// supplied the check narrows from a bare <c>cursor.Current is Wrapper</c> to
/// <c>(cursor.Current is Wrapper owningMembershipN &amp;&amp; owningMembershipN.OwnedRelatedElement.OfType&lt;Inner&gt;().Any())</c>
/// so the discriminator is precise enough to distinguish a thin owning wrapper from
/// sibling subtypes of the same wrapper class. The pattern-variable suffix is drawn from
/// <see cref="RuleGenerationContext.NarrowedTypeCheckCounter"/> so multiple narrowed
/// checks in the same generated method do not collide on the C# scope (CS0136).
/// </summary>
/// <param name="cursorVariableName">The cursor variable name in scope at the emission site.</param>
/// <param name="wrapperType">The fully-qualified wrapper type name.</param>
/// <param name="innerType">The fully-qualified inner element type name when narrowing applies; otherwise <see langword="null" />.</param>
/// <param name="ruleGenerationContext">The current <see cref="RuleGenerationContext" /> providing the per-rule counter for unique pattern-variable names.</param>
/// <returns>The C# boolean expression to emit.</returns>
private static string BuildTypeCheck(string cursorVariableName, string wrapperType, string innerType, RuleGenerationContext ruleGenerationContext)
{
if (innerType == null)
{
return $"{cursorVariableName}.Current is {wrapperType}";
}

var patternVariableName = $"owningMembership{ruleGenerationContext.NarrowedTypeCheckCounter}";
ruleGenerationContext.NarrowedTypeCheckCounter++;

return $"({cursorVariableName}.Current is {wrapperType} {patternVariableName} && {patternVariableName}.OwnedRelatedElement.OfType<{innerType}>().Any())";
}

/// <summary>
/// Returns the cursor variable name for <paramref name="property" />, reusing an
/// already-declared cursor when one is present in
Expand Down Expand Up @@ -532,7 +567,7 @@ private static string EnsureCursorDeclared(EncodedTextWriter writer, IProperty p
/// <summary>
/// Emits an optional condition wrapping block for an optional NonTerminal element.
/// </summary>
private bool TryEmitOptionalCondition(EncodedTextWriter writer, NonTerminalElement nonTerminalElement, TextualNotationRule referencedRule, IClass targetClass, RuleGenerationContext ruleGenerationContext, string variableName)
private static bool TryEmitOptionalCondition(EncodedTextWriter writer, NonTerminalElement nonTerminalElement, TextualNotationRule referencedRule, IClass targetClass, RuleGenerationContext ruleGenerationContext, string variableName)
{
if (!nonTerminalElement.IsOptional || nonTerminalElement.IsCollection)
{
Expand All @@ -544,7 +579,7 @@ private bool TryEmitOptionalCondition(EncodedTextWriter writer, NonTerminalEleme
return false;
}

var condition = this.GenerateInlineOptionalCondition(writer, referencedRule, targetClass, ruleGenerationContext, variableName);
var condition = GenerateInlineOptionalCondition(writer, referencedRule, targetClass, ruleGenerationContext, variableName);

if (condition == null)
{
Expand Down
Loading
Loading