Skip to content

[net11.0][XSG] Trimmable Styles#33561

Open
simonrozsival wants to merge 14 commits into
net11.0from
dev/simonrozsival/trimmable-styles
Open

[net11.0][XSG] Trimmable Styles#33561
simonrozsival wants to merge 14 commits into
net11.0from
dev/simonrozsival/trimmable-styles

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Jan 16, 2026

Copy link
Copy Markdown
Member

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description

Implements lazy/trimmable styles for XAML Source Generation, enabling the IL trimmer to remove unused styles and their target types from compiled apps.

Fixes #33156

How It Works

New Style Constructor and LazyInitialization Property

// New constructor accepts assembly-qualified type name (trim-safe)
public Style(string assemblyQualifiedTargetTypeName) { ... }

// Initializer delegate called lazily on first application  
public Action<Style, BindableObject> LazyInitialization { private get; set; }

Generated Code Pattern

The source generator now emits:

var style = new Style("Microsoft.Maui.Controls.Label, Microsoft.Maui.Controls");
style.LazyInitialization = (__style, __target) =>
{
    // Type guard - skips if Label was trimmed away
    if (__target is not global::Microsoft.Maui.Controls.Label) return;
    
    // Setters populated here
    var setter = new Setter { Property = Label.TextColorProperty, Value = Colors.Red };
    __style.Setters.Add(setter);
};

Key Implementation Details

  • IStyle.TargetType returns Type? (nullable) - returns null if type was trimmed
  • Style.ResolveTargetType() uses [UnconditionalSuppressMessage] to suppress IL2057 - intentionally allows types to be trimmed
  • Style.InitializeIfNeeded(target) called on first IStyle.Apply() - runs the initializer once with proper locking
  • Style.TargetTypeFullName provides trim-safe type comparison without resolving the Type

Changes Summary

Area Key Changes
Style.cs New constructor, LazyInitialization property, InitializeIfNeeded(), ResolveTargetType()
IStyle.cs TargetType is now nullable (Type?)
SourceGen All visitors updated with StopOnStyle/VisitNodeOnStyle/IsStyle for special Style handling
XamlC Same visitor interface changes (XamlC does NOT use lazy styles)
Tests Comprehensive unit tests for lazy style behavior

What This Enables

  1. Trimmer can remove unused styles - If a style's target type is trimmed, TargetType returns null and the style is skipped at runtime
  2. Deferred initialization - Setters/Behaviors/Triggers are only created when the style is first applied
  3. Type guards - Generated code includes if (__target is not TargetType) return; to handle trimmed types gracefully

TODO

  • Verify trimming actually works end-to-end with a trimmed publish
  • Full UI test validation
  • Consider dedicated UI test for implicit style BasedOn/ApplyToDerivedTypes behavior

@simonrozsival simonrozsival changed the title [WIP] Trimmable/Lazy Styles for SourceGen [WIP][XSG] Trimmable/Lazy Styles for SourceGen Jan 16, 2026
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-styles branch from bfcfe86 to 01cf3de Compare January 16, 2026 13:21
@simonrozsival simonrozsival changed the title [WIP][XSG] Trimmable/Lazy Styles for SourceGen [WIP][net11.0][XSG] Trimmable/Lazy Styles for SourceGen Jan 16, 2026
@simonrozsival simonrozsival changed the base branch from main to net11.0 January 16, 2026 13:22
@simonrozsival simonrozsival added xsg Xaml sourceGen perf/app-size Application Size / Trimming (sub: perf) labels Jan 16, 2026
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-styles branch 6 times, most recently from 64a71d2 to eab10eb Compare January 16, 2026 14:48
@simonrozsival simonrozsival changed the title [WIP][net11.0][XSG] Trimmable/Lazy Styles for SourceGen [net11.0][XSG] Trimmable Styles Jan 20, 2026
@simonrozsival simonrozsival marked this pull request as ready for review January 20, 2026 12:47
Copilot AI review requested due to automatic review settings January 20, 2026 12:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR implements lazy/trimmable styles for XAML Source Generation, enabling the IL trimmer to remove unused styles and their target types from compiled apps. The feature addresses issue #33156 by deferring style initialization until first application and using string-based type names instead of Type objects for AOT compatibility.

Changes:

  • Added new Style(string assemblyQualifiedTargetTypeName) constructor and LazyInitialization property for deferred initialization
  • Modified IStyle.TargetType to be nullable (Type?) to support trimmed types
  • Updated source generator to emit lazy style initialization code with type guards
  • Extended visitor infrastructure with StopOnStyle, VisitNodeOnStyle, and IsStyle methods across all visitors (both SourceGen and XamlC)

Reviewed changes

Copilot reviewed 47 out of 47 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Style.cs Adds lazy constructor, LazyInitialization property, nullable TargetType resolution, and TargetTypeFullName for trim-safe type comparison
IStyle.cs Changes TargetType to nullable Type? to support trimmed types
MergedStyle.cs Updates to handle nullable TargetType from IStyle
ResourceDictionary.cs Uses TargetTypeFullName instead of TargetType.FullName for adding styles
CreateValuesVisitor.cs Implements TryCreateTrimmableStyle to generate string-based Style constructor
SetPropertiesVisitor.cs Generates LazyInitialization lambda with type guard before style property assignment
SetNamescopesAndRegisterNamesVisitor.cs Adds null checks for nodes not in Variables dictionary (Style content)
XamlNode.cs Implements IsStyleContent logic for skipping Style children during normal traversal
XmlName.cs Adds _StyleContent marker for identifying styles needing lazy initialization
All visitor files (20+) Implements StopOnStyle, VisitNodeOnStyle, and IsStyle interface methods
PublicAPI files (7) Adds new public API entries for Style constructor and LazyInitialization setter
Test files (10+) Updates tests to call InitializeIfNeeded for SourceGen inflator, adds comprehensive lazy style tests

return false;
do
{
targetType = targetType.BaseType;

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

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

The CanBeAppliedTo method has a potential null reference exception. When checking ApplyToDerivedTypes, the code iterates through the base types and calls targetType.FullName without checking if targetType is null. If targetType.BaseType returns null (which happens when reaching the top of the type hierarchy), the code will throw a NullReferenceException on the next iteration when accessing targetType.FullName.

Add a null check before accessing targetType.FullName in the loop.

Suggested change
targetType = targetType.BaseType;
targetType = targetType.BaseType;
if (targetType == null)
return false;

Copilot uses AI. Check for mistakes.
Comment thread src/Controls/src/Core/Style.cs Outdated
Comment on lines +164 to +168
return _targetType.FullName.AsSpan();

// Extract FullName from AQN: "Namespace.TypeName, AssemblyName, ..."
// FullName is everything before the first comma
Debug.Assert(_assemblyQualifiedTargetTypeName is not null, "Either _targetType or _assemblyQualifiedTargetTypeName must be set");

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

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

The TargetTypeFullName property may throw a NullReferenceException when _targetType is not null but _targetType.FullName is null. For generic types or special type scenarios, Type.FullName can return null according to the .NET documentation. This would cause AsSpan() to throw.

Add a null check for _targetType.FullName before calling AsSpan(), or handle the null case by falling back to the assembly-qualified name extraction logic.

Suggested change
return _targetType.FullName.AsSpan();
// Extract FullName from AQN: "Namespace.TypeName, AssemblyName, ..."
// FullName is everything before the first comma
Debug.Assert(_assemblyQualifiedTargetTypeName is not null, "Either _targetType or _assemblyQualifiedTargetTypeName must be set");
{
var fullName = _targetType.FullName;
if (fullName is not null)
return fullName.AsSpan();
// If FullName is null (e.g., for certain generic or special types),
// fall back to the assembly-qualified name parsing below.
}
// Extract FullName from AQN: "Namespace.TypeName, AssemblyName, ..."
// FullName is everything before the first comma
Debug.Assert(_assemblyQualifiedTargetTypeName is not null, "When _targetType.FullName is null, _assemblyQualifiedTargetTypeName must be set");

Copilot uses AI. Check for mistakes.
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-styles branch from e7d801f to d0c0ce4 Compare January 20, 2026 13:31
@rmarinho

Copy link
Copy Markdown
Member

/rebase

@simonrozsival

Copy link
Copy Markdown
Member Author

@copilot this PR needs a proper rebase with conflict resolution. open a PR which addresses this issue. make sure it is up to date with the base net11.0 branch.

Copilot AI commented Feb 12, 2026

Copy link
Copy Markdown
Contributor

@simonrozsival I've opened a new pull request, #34011, to work on those changes. Once the pull request is ready, I'll request review from you.

@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-styles branch from 3536086 to fefc63a Compare February 13, 2026 08:22
simonrozsival added a commit that referenced this pull request Feb 18, 2026
…hanges

These changes belong in separate PRs:
- #33611 (CSS StyleSheet trimming)
- #33561 (EventTrigger trimming)
- #33160 (HybridWebView trimming)

Reverted files to their main branch state, keeping only the
[ElementHandler] attribute addition on HybridWebView.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@StephaneDelcroix StephaneDelcroix force-pushed the dev/simonrozsival/trimmable-styles branch from fefc63a to 59a9982 Compare February 27, 2026 15:27
@github-actions

github-actions Bot commented Feb 27, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 33561

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 33561"

@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-styles branch from 59a9982 to 9fb44d8 Compare March 11, 2026 10:00
@simonrozsival

Copy link
Copy Markdown
Member Author

Self-review notes

1. Extract helper for repeated ((IStyle)style).Apply(...) pattern

The pattern ((IStyle)style).Apply(new BoxView(), new SetterSpecificity()) appears 6+ times in the style tests. Add a helper method to reduce boilerplate:

static void ForceApplyLazyStyle(Style style, BindableObject target)
{
    ((IStyle)style).Apply(target, new SetterSpecificity());
}

2. Revert unnecessary whitespace changes

These files have whitespace-only diffs (extra blank line added) unrelated to this PR:

  • src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SetterCompiledConverters.cs
  • src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs
  • src/Controls/tests/SourceGen.UnitTests/Maui32879Tests.cs

3. Duplicate ShellFlyoutRenderer entry in PublicAPI.Unshipped.txt

The net-ios and net-maccatalyst PublicAPI.Unshipped.txt files already contain:

~override Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutRenderer.ViewWillTransitionToSize(...) -> void

This PR duplicates that line. Remove the duplicate — it is unrelated to the style changes.

@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-styles branch from 29da551 to a14c299 Compare March 20, 2026 09:08
@simonrozsival

Copy link
Copy Markdown
Member Author

@StephaneDelcroix @rmarinho could you please have a look at this PR and let me know what you think about it? could we merge this into net11.0 soon?

simonrozsival and others added 9 commits March 31, 2026 19:32
Squashed for clean rebase.
- Inline the lazy initialization lock+check directly into IStyle.Apply
- Remove the internal InitializeIfNeeded method (was only needed for tests)
- Update tests to use ((IStyle)style).Apply() instead of InitializeIfNeeded

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Duplicate was introduced during rebase conflict resolution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- CanBeAppliedTo: add null check after BaseType walk (reaches null at
  top of hierarchy before hitting Element)
- TargetTypeFullName: guard against Type.FullName being null for
  generic/special types; fall through to AQN parsing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- PublicAPI net-ios/net-maccatalyst: remove duplicate ShellFlyoutRenderer entry
- Revert whitespace-only changes in 3 SourceGen test snapshot files

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace 6 occurrences of ((IStyle)style).Apply(target, new SetterSpecificity())
with style.ForceInitialize(target) extension method.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- SetPropertiesVisitor: remove duplicate variable declarations and
  duplicate getNodeValue delegate from rebase conflict
- SetNamescopesAndRegisterNames: remove duplicate if-statement, keep
  TryGetValue guard for nodes not in Variables
- Style.CanBeAppliedTo: guard against null Type.FullName

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Change TargetTypeFullName from ReadOnlySpan<char> to string, removing
  #if NETSTANDARD branching and Span-based comparisons
- Use plain string equality in CanBeAppliedTo instead of SequenceEqual
- Remove redundant .ToString() call in ResourceDictionary.Add
- Revert unrelated maps PublicAPI.Unshipped.txt changes from rebase

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rename parentVar in nested scope to avoid CS0136.
Add StringComparison.Ordinal to IndexOf call to satisfy CA1307.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-styles branch from fe637c1 to 2b30cbd Compare March 31, 2026 17:34
- SetPropertiesVisitor.cs: Use Context property (not primary ctor param)
  since this class uses a regular constructor
- Style.cs: Remove StringComparison.Ordinal from char IndexOf overload
  for netstandard2.0 compatibility

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
if (!ApplyToDerivedTypes)
return false;
do
while (targetType != typeof(Element))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 The do-whilewhile refactor here changed semantics. If targetType is exactly typeof(Element) and ApplyToDerivedTypes=true, the old code entered the loop body first (getting BaseType), but the new while loop checks the condition first — so it never enters.

Old behavior:

do {
    targetType = targetType.BaseType;       // Element → NavigableElement
    if (TargetType == targetType) return true; // match!
} while (targetType != typeof(Element));

New behavior:

while (targetType != typeof(Element))  // Element == Element → false, skip loop
{
    // never reached
}
return false;  // ← regression

Suggested fix — restore do-while with the null guard:

do
{
    targetType = targetType.BaseType;
    if (targetType is null)
        return false;
    if (targetType.FullName is not null && TargetTypeFullName == targetType.FullName)
        return true;
} while (targetType != typeof(Element));
return false;

@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-styles branch from d909db6 to 3a76e7f Compare March 31, 2026 20:49
simonrozsival and others added 3 commits April 1, 2026 08:58
- StyleSourceGenTests: Reorder __root before style in snapshots to match
  upstream SourceGen visit order change
- SetterCompiledConverters, SimplifyOnPlatform, Maui32879: Convert
  fragile full-snapshot tests to assertion-based tests validating the
  trimmable style pattern (string ctor, LazyInitialization, setters)
- LazyStyleTestHelper: Call LazyInitialization directly instead of
  IStyle.Apply to avoid InvalidCastException when style target type
  differs from the test element type

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kubaflo

kubaflo commented May 24, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/refactor-copilot-yml

@MauiBot

MauiBot commented May 24, 2026

Copy link
Copy Markdown
Collaborator

⚠️ Merge Conflict Detected — This PR has merge conflicts with its target branch. Please rebase onto the target branch and resolve the conflicts.

@kubaflo

kubaflo commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

AI code review for net11.0 target

Verdict: Needs changes (well-designed, but gated on unverified unit-test failures)

This is a non-approval, automated review comment. It is advisory only — human approval is required.

What this PR does

Implements lazy/trimmable styles for XAML Source Generation (#33156): a new Style(string assemblyQualifiedTargetTypeName) ctor plus a LazyInitialization delegate that populates Setters/Triggers/Behaviors on first apply, so the IL trimmer can drop unused styles and their target types.

Findings

  • Solid, internally consistent design. IStyle.TargetType becomes nullable; Style.TargetType lazily resolves via Type.GetType(aqtn, throwOnError: false) (returning null when trimmed) with an appropriate UnconditionalSuppressMessage justification; LazyInitialization is invoked once under _initializerLock in IStyle.Apply. ResourceDictionary.Add and MergedStyle switch to TargetTypeFullName/null-safe checks to avoid forcing type resolution. Good test coverage added (SourceGen + Core + Xaml unit tests).
  • CanBeAppliedTo now matches by FullName instead of reference equality. Intentional for trimming, but slightly looser — two types sharing a FullName across assemblies would now compare equal. Low risk; worth a conscious note.
  • do/whilewhile rewrite in CanBeAppliedTo changes the loop boundary for the edge case where targetType is already typeof(Element) (it's no longer walked above Element). Normal control hierarchies behave the same; please confirm the edge case is intended.
  • TargetType getter uses _targetType ??= ResolveTargetType() without synchronization — benign (idempotent double-resolve), just noting.
  • The bulk of SourceGen/SetPropertiesVisitor.cs is a mechanical context (primary-ctor capture) → Context property refactor needed to add the stopOnStyle ctor arg; it reads correctly.

CI note

maui-pr (Run Helix Unit Tests Windows — Debug & Release) are failing. Those jobs run exactly the Controls.Core.UnitTests / Xaml.UnitTests / SourceGen.UnitTests suites this PR modifies, so the failures could be PR-caused (including newly added tests). I could not retrieve per-test detail — the timeline for build 1361658 has been purged. Please confirm these unit suites are green (or demonstrably pre-existing/flaky) before merge. The AOT macOS integration failure also appears on several unrelated PRs and is likely infra.

Confidence: Medium. The design and diff look correct; the verdict is driven by unverifiable unit-test failures on the exact projects this PR changes.

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR #33561 — [net11.0][XSG] Trimmable Styles (sibling of #33611)

Verdict: NEEDS_CHANGES (confidence: high). Strong, well-structured work — the lazy/trimmable Style shape is coherent, the three XAML pipelines (Build.Tasks/XamlC, SourceGen, runtime) were updated consistently, and PublicAPI is in order. But all four models converged on a TargetType-serialization bug that breaks parity for generic/nested target types, plus a lazy-init lifecycle edge case.

Blocking (inline)

  1. ❌ TargetType serialized as a C# display name, not a CLR AQN (CreateValuesVisitor.cs:432). ToDisplayString(FullyQualifiedFormat) produces Foo<Bar> / Outer.Inner, but Type.GetType (runtime, Style.cs:182) needs Foo`1[[…]] / Outer+Inner. For generic/nested target types it returns null and the style is silently skipped (no diagnostic), and the same malformed name breaks TargetTypeFullName + implicit/StyleClass key matching. Unanimous across all 4 models + code-verified.
  2. ⚠️ Lazy Style can be permanently consumed if first applied to an incompatible element (Style.cs:149). The generated initializer is type-guarded (if (__target is not T) return;), but IStyle.Apply nulls LazyInitialization unconditionally after the first call — so a mismatched-first-apply returns early without initializing yet still consumes the initializer, silently breaking the style for later correct elements. (opus-4.8 + opus-4.6.)

Notes / lower-confidence (not inline)

  • gemini flagged implicit-style detection node.XmlType.Name == "Style" (SetPropertyHelpers.cs:165) as potentially too narrow — worth a quick check that aliased/subclassed style elements are still detected, though the common case is fine.
  • The simple-type path (the vast majority of styles) works correctly; the serialization bug is scoped to generic/nested TargetType. The silent-skip behavior is what elevates it to a blocker — a clear diagnostic (or correct AQN) would prevent a hard-to-debug "style just doesn't apply."

CI

Red legs are the usual unrelated infra flakes; the generator-correctness items above are the blockers.

(opus-4.8 initially returned a placeholder but completed on retry; synthesis reflects all 4 models + direct code verification. This mirrors the target-type-resolution class of issue found in the sibling EventTrigger XSG PR #33611.)

// Generate the assembly-qualified name (without global:: prefix, with assembly name)
// Type.GetType() expects format: "Namespace.TypeName, AssemblyName"
#pragma warning disable RS0030 // Use banned ToDisplayString to get name without global:: prefix
var typeFullName = targetType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The TargetType name handed to the lazy Style constructor is a C# display name, not a CLR assembly-qualified name — so generic/nested target types silently break. Line 432 builds the name via targetType.ToDisplayString(FullyQualifiedFormat) and line 434 appends , {assembly}. That's only a valid Type.GetType string for simple types. For:

  • generic target types, C# syntax is Ns.Foo<Ns.Bar> but the CLR/AQN form is Ns.Foo`1[[Ns.Bar, AsmBar]], AsmFoo;
  • nested target types, C# uses Ns.Outer.Inner but the CLR form is Ns.Outer+Inner.

At runtime Style.GetTargetType() calls Type.GetType(_assemblyQualifiedTargetTypeName, throwOnError: false) (Style.cs:182). For these shapes it returns null, and per the suppression note at Style.cs:177-178 a null target type means the style is silently skipped — no diagnostic, the style just doesn't apply, diverging from XamlC/runtime. The same malformed name also flows into TargetTypeFullName (Style.cs:188-204, which slices at the first comma) and is compared against targetType.FullName for implicit/class-style key matching (Style.cs:220/229), so implicit styles and StyleClass matching on generic/nested types are wrong too. Generate a real assembly-qualified name (generic-arg AQNs + nested +), or store the runtime-format full name separately, rather than ToDisplayString(). (Unanimous across all 4 models; confirmed in code.)

if (LazyInitialization is not null)
{
LazyInitialization(this, bindable);
LazyInitialization = null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A shared lazy Style can be permanently “consumed” (and silently never initialized) if it is first applied to an incompatible element. In IStyle.Apply the LazyInitialization delegate is invoked with the first bindable and then set to null unconditionally (lines 146-150). The generated initializer begins with a type guard — if (__target is not TargetType) return;. So if this same Style instance is first applied to a non-matching element (e.g. an explicit Style="{StaticResource X}" mistakenly set on a wrong-typed view, or any path that applies the shared instance to an incompatible type before a compatible one), the initializer returns early without wiring up setters/triggers, yet LazyInitialization is still nulled — so every subsequent Apply to a correct element finds LazyInitialization == null and the style does nothing, with no error. Only null out LazyInitialization once initialization actually ran (e.g. have the generated delegate report success, or move the type guard out so a mismatch doesn't consume the initializer). (opus-4.8 + opus-4.6.)

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

Labels

perf/app-size Application Size / Trimming (sub: perf) xsg Xaml sourceGen

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[XSG] Generate trimmable styles

7 participants