Repository navigation
Controls: Remove Regex from ShadowTypeConverter - #38615
Conversation
Replace runtime Regex tokenization with an allocation-conscious inline parser while preserving the existing accepted grammar and legacy tokenization behavior. Add focused compatibility tests for colors, numbers, whitespace, malformed input, and invariant culture. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 38615Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 38615" |
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
Inline invariant-culture float parsing and remove the unnecessary helper. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use char.IsAsciiLetter on modern targets while retaining the equivalent netstandard fallback. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use char.IsAsciiHexDigit on modern targets while retaining the equivalent netstandard fallback. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Replace paired integer slots with a small stack-allocated Part value type for clearer token handling. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Two moderate parser issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Replaces runtime regex parsing in ShadowTypeConverter with an inline tokenizer and expands shadow parsing tests.
Changes:
- Adds manual color and numeric tokenization.
- Expands coverage for valid, malformed, whitespace, and culture-specific inputs.
- Reports reduced linked assembly size.
- Numeric parsing still allocates substrings, and whitespace handling differs from the previous regex behavior.
| File | Summary |
|---|---|
src/Controls/src/Core/ShadowTypeConverter.cs |
Replaces regex parsing; requires span-based numeric parsing and regex-compatible whitespace handling. |
src/Controls/tests/Core.UnitTests/ShadowTests.cs |
Adds comprehensive parser and edge-case coverage. |
Add a project-specific banned API rule to prevent System.Text.RegularExpressions from being reintroduced into Microsoft.Maui.Controls. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Note 🔍
|
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
@simonrozsival — new AI review results are available based on commit
7834a02.
🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix
Gate Result: ❌ FAILED
Platform: ANDROID · Base: main · Merge base: 49b62062
🩺 Test does not reproduce the bug — ran the same in both states (PASS without fix, PASS with fix). The repro test is not exercising the issue. Strengthen the test before reviewing the fix.
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🧪 ShadowTests ShadowTests |
❌ PASS — 224s | ✅ PASS — 124s |
🔴 Without fix — 🧪 ShadowTests: PASS ❌ · 224s
(no coded error found; showing last 1200 chars)
s]
Passed ShadowTypeConverter_AcceptsSupportedColorSyntax(value: "rgba(100%, 0%, 0%, 0.5) 1 2") [< 1 ms]
Passed ShadowTypeConverter_AcceptsSupportedColorSyntax(value: "hsla(120, 100%, 50%, 0.5) 1 2") [< 1 ms]
Passed ShadowTypeConverter_AcceptsSupportedColorSyntax(value: "rgb(255, 0, 0) 1 2") [< 1 ms]
Passed ShadowTypeConverter_AcceptsSupportedColorSyntax(value: "AliceBlue 1 2") [< 1 ms]
Passed ShadowTypeConverter_AcceptsSupportedColorSyntax(value: "hsva(120, 100%, 50%, 0.5) 1 2") [< 1 ms]
Passed ShadowTypeConverter_AcceptsSupportedColorSyntax(value: "hsv(120, 100%, 50%) 1 2") [< 1 ms]
Passed ShadowTypeConverter_AcceptsRegexWhitespace(value: "rgba(100%, 32%, 64%, 0.27) 4 4") [< 1 ms]
[xUnit.net 00:00:02.80] Finished: Microsoft.Maui.Controls.Core.UnitTests
Passed ShadowTypeConverter_AcceptsRegexWhitespace(value: "hsl(6, 20%, 45%)\n1\t5") [< 1 ms]
Passed ShadowTypeConverter_AcceptsRegexWhitespace(value: "rgb(\t6,\r\n201, 198) 4 4") [< 1 ms]
Passed ShadowTypeConverter_AcceptsRegexWhitespace(value: "hsva(6, 20%, 45%, 0.75)\r\n6 3") [< 1 ms]
Passed ShadowInitializesCorrectly [7 ms]
Test Run Successful.
Total tests: 85
Passed: 85
Total time: 3.6758 Seconds
🟢 With fix — 🧪 ShadowTests: PASS ✅ · 124s
(no coded error found; showing last 1200 chars)
]
Passed ShadowTypeConverter_AcceptsSupportedColorSyntax(value: "rgba(100%, 0%, 0%, 0.5) 1 2") [< 1 ms]
Passed ShadowTypeConverter_AcceptsSupportedColorSyntax(value: "hsla(120, 100%, 50%, 0.5) 1 2") [< 1 ms]
Passed ShadowTypeConverter_AcceptsSupportedColorSyntax(value: "rgb(255, 0, 0) 1 2") [< 1 ms]
Passed ShadowTypeConverter_AcceptsSupportedColorSyntax(value: "AliceBlue 1 2") [< 1 ms]
Passed ShadowTypeConverter_AcceptsSupportedColorSyntax(value: "hsva(120, 100%, 50%, 0.5) 1 2") [< 1 ms]
Passed ShadowTypeConverter_AcceptsSupportedColorSyntax(value: "hsv(120, 100%, 50%) 1 2") [< 1 ms]
Passed ShadowTypeConverter_AcceptsRegexWhitespace(value: "rgba(100%, 32%, 64%, 0.27) 4 4") [< 1 ms]
[xUnit.net 00:00:02.64] Finished: Microsoft.Maui.Controls.Core.UnitTests
Passed ShadowTypeConverter_AcceptsRegexWhitespace(value: "hsl(6, 20%, 45%)\n1\t5") [< 1 ms]
Passed ShadowTypeConverter_AcceptsRegexWhitespace(value: "rgb(\t6,\r\n201, 198) 4 4") [< 1 ms]
Passed ShadowTypeConverter_AcceptsRegexWhitespace(value: "hsva(6, 20%, 45%, 0.75)\r\n6 3") [< 1 ms]
Passed ShadowInitializesCorrectly [17 ms]
Test Run Successful.
Total tests: 85
Passed: 85
Total time: 3.3422 Seconds
⚠️ Failure Details
- ❌ ShadowTests PASSED without fix (should fail) — tests don't catch the bug
📁 Fix files reverted (2 files)
src/Controls/src/Core/Controls.Core.csprojsrc/Controls/src/Core/ShadowTypeConverter.cs
New files (not reverted):
src/Controls/src/Core/BannedSymbols.txt
📋 Pre-Flight — Context & Validation
PR #38615 Pre-Flight
Context
- Title: Controls: Remove Regex from ShadowTypeConverter
- PR: #38615
- Base / head:
main/simonrozsival-remove-shadow-regex - Author:
simonrozsival - Problem:
ShadowTypeConverterconstructs a runtimeRegexfor every conversion. That reference rootsSystem.Text.RegularExpressionsimplementation code in trimmed MAUI Android applications. The goal is to remove the Controls dependency on regular expressions while preserving the converter's existing parsing behavior. - Linked issue: None.
- Platform: Android.
Direct Diff Inspection
The PR changes four files:
src/Controls/src/Core/ShadowTypeConverter.csreplaces the runtime, unanchoredRegex.Matchesimplementation with an allocation-conscious tokenizer. It scans aReadOnlySpan<char>, records at most five stack-allocated token boundaries, and has dedicated readers for numbers, hex colors, RGB/RGBA, HSL/HSLA, HSV/HSVA, and named colors.src/Controls/src/Core/Controls.Core.csprojregisters a project-specific banned-symbols file.src/Controls/src/Core/BannedSymbols.txtis a new file banningSystem.Text.RegularExpressions.src/Controls/tests/Core.UnitTests/ShadowTests.csexpands the converter regression matrix for supported color forms, invariant numeric parsing, Unicode/regex whitespace, permissive punctuation, malformed input, and legacy unanchored/greedy regex edge cases.
The current approach is specifically a scalar, hand-written compatibility parser with stack-allocated token metadata. Alternative candidates must use a different mechanism/root-cause strategy rather than merely rearranging these helper methods.
Gate and Test Scope
- The prior gate is authoritative and must not be rerun.
- Gate result: FAILED because
ShadowTestspassed without the fix as well as with it. This is expected for a compatibility suite and does not directly measure the trimming/dependency objective. - Detected primary regression test: all
ShadowTestsinsrc/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj. - Targeted command:
dotnet test src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj --filter "FullyQualifiedName~ShadowTests" - No additional mandatory regression tests were supplied in the STEP 5a request; no full suite is permitted.
Try-Fix Boundary
The production target files are:
src/Controls/src/Core/ShadowTypeConverter.cssrc/Controls/src/Core/Controls.Core.csprojsrc/Controls/src/Core/BannedSymbols.txt
BannedSymbols.txt is new in the PR. The try-fix restoration contract requires an attempt to report Blocked before editing whenever .github/.baseline-state.json contains any NewFiles; each child must enforce that boundary and still capture artifacts and run the exact restore script. The existing public API is unchanged.
🔬 Code Review — Deep Analysis
Code Review — PR #38615
Independent Assessment
What this changes: Replaces ShadowTypeConverter's per-conversion, unanchored Regex.Matches tokenizer with a bounded span-based scanner, wires a Controls.Core banned-symbol rule for System.Text.RegularExpressions, and expands compatibility tests.
Inferred motivation: Remove a regex reachability root and its linked-size cost from trimmed MAUI applications without changing the converter's permissive legacy token extraction.
Reconciliation with PR Narrative
Author claims: The PR removes the Controls regex root, preserves legacy parsing behavior, adds a project-specific dependency ban and compatibility tests, and reduces linked Android output size.
Agreement/disagreement: The submitted diff supports the implementation and compatibility claims. The externally measured Android size results were not independently reproduced.
Prior Review Reconciliation
No prior ❌ Error findings were found across top-level reviews, inline review comments, or PR issue comments. A prior inline suggestion about per-token string materialization is an accepted non-blocking tradeoff: the old implementation also materialized Match.Value, and removing regex reachability does not require cross-target span parsing.
Blast Radius Assessment
- Runs for all instances: The converter implementation changes for every string conversion, but only when
ShadowTypeConverteris invoked. - Startup impact: None; no startup registration or static initialization was added.
- Static/shared state: None.
- Null/default state: Null retains the existing
ArgumentNullExceptionpath; incomplete token sets are rejected.
CI Status
- Required-check result: Unavailable.
gh pr checks 38615 --repo dotnet/maui --requiredreturned no rows because GitHub CLI authentication is unavailable. - Trusted Gate result: FAILED because all 85
ShadowTestspassed both without and with the production fix. - Classification: The tests establish parser compatibility but do not discriminate the regex-dependency objective; this is an evidence gap, not a with-fix parser regression.
- Action taken: The Gate was not rerun, and confidence is capped at low.
Findings
No actionable file:line findings. The expert reviewer verified the submitted parser against the former alternative ordering, greedy hexadecimal matching, incomplete decimal/exponent rollback, Unicode whitespace and digit handling, ignored punctuation, exception normalization, analyzer wiring, and focused tests.
Failure-Mode Probing
- Inputs without shadow syntax: The scanner either finds no supported token sequence or downstream color/number conversion rejects it with the existing
InvalidOperationExceptioncontract. - More than five recognized tokens:
Tokenizereturns a count greater than five without indexing beyond the stack-allocated span, so conversion rejects the input. - Malformed token prefixes: Failed structured-color readers fall back to the same unanchored scanning behavior rather than consuming partial structured tokens as valid colors.
- Null/default and lifecycle state: The converter is stateless apart from its existing color converter; this change adds no startup path, shared mutable state, subscriptions, handlers, or platform-view dependencies.
External Output Contract
Not applicable.
Verdict: NEEDS_DISCUSSION
Confidence: low
Summary: The raw submitted implementation is a sound, allocation-conscious replacement for the prior runtime regex, and the expert pass produced [] in inline-findings.json; no reviewer refinement is warranted from code findings. Approval is not supported by this run because required-check status is unavailable and the trusted Gate does not prove the dependency-removal objective.
🛠️ Try-Fix — Analysis & Comparison
Try-Fix Aggregate
Candidate 1 — Generated Regex Matcher
- Model:
gpt-5.3-codex - Approach: Replace the per-conversion
new Regex(...)with a cached source-generated[GeneratedRegex]matcher. This retains regex-driven, unanchoredMatchestoken discovery rather than duplicating regex behavior in the PR's scalarReadOnlySpan<char>tokenizer. - Mechanism-level difference: The PR removes regex through a custom lexical parser. This candidate would preserve the regex engine's matching semantics but move pattern construction to compile time and cache the matcher.
- Production diff: None.
- Test result: Blocked before testing.
- Failure analysis:
EstablishBrokenBaseline.ps1recorded the PR-addedsrc/Controls/src/Core/BannedSymbols.txtin a non-emptyNewFilesarray. The try-fix restoration contract therefore prohibited all production edits. The targetedShadowTestscommand was not run. - Self-review: 0 findings.
- Restore: The exact script-only restore command completed.
- Detailed narrative:
../try-fix-1/content.md
Candidate 1 is complete and must not be reopened or rerun. Candidate 2 must avoid both the PR's hand-written span tokenizer and this source-generated/cached-regex strategy.
Candidate 2 — Table-Driven DFA
- Model:
gpt-5.6-sol - Approach: Replace regex matching with a generic, table-driven deterministic finite automaton whose transitions and actions reproduce the ordered, greedy, unanchored token discovery rules.
- Mechanism-level difference: Unlike the PR's syntax-specific span readers, this candidate would interpret generic transition/action tables. Unlike Candidate 1, it would not use or retain
System.Text.RegularExpressions; matching would be performed entirely by the DFA runtime. - Production diff: None.
- Test result: Blocked before testing.
- Failure analysis:
EstablishBrokenBaseline.ps1again recorded the PR-addedsrc/Controls/src/Core/BannedSymbols.txtin a non-emptyNewFilesarray. The try-fix restoration contract prohibited production edits, so the targetedShadowTestscommand was not run and an empty test log was captured. - Self-review: 0 findings.
- Restore: The exact script-only restore completed with
Restored True; target files were reported clean. - Detailed narrative:
../try-fix-2/content.md
Aggregate Outcome
Two candidates were attempted in the required order and no more candidates were launched. Both are distinct from the PR and from each other, but neither could be implemented or empirically tested because the try-fix baseline safety contract cannot restore PR-added production files. Consequently, STEP 5a has two Blocked candidates, zero passing candidates, and no candidate production diff for STEP 5b to select.
🏁 Report — Final Recommendation
⚠️ Final Recommendation: REQUEST CHANGES
Winner: pr
The submitted PR is the single best candidate. It is the only candidate with a complete implementation, its 85 focused ShadowTests pass with the fix, the expert reviewer found no actionable correctness issue, and the regression cross-reference is clean. pr-plus-reviewer is byte-for-byte equivalent because the expert review produced no feedback to apply; selecting that no-op variant would misrepresent it as an improvement.
Approval is nevertheless blocked by the trusted Gate. The same 85 tests also pass without the production fix, so they validate compatibility but do not demonstrate the dependency-removal regression. A focused check that fails when Controls.Core references System.Text.RegularExpressions and passes when that root is removed is still needed before approval.
Comparative ranking
| Rank | Candidate | Implementation | Targeted regression evidence | Expert assessment | Result |
|---|---|---|---|---|---|
| 1 | pr |
Complete span-based tokenizer plus banned-symbol wiring | 85/85 pass with fix, but also pass without fix; trusted Gate FAILED | LGTM, medium confidence, 0 inline findings | Winner, but not approvable under the Gate |
| 2 | pr-plus-reviewer |
Identical to pr; reviewer.patch is empty |
Not rerun because there was no reviewer change | No actionable feedback to apply | Sound but provides no advantage over pr |
| 3 | try-fix-1 |
Proposed cached/source-generated regex only; no production diff | Blocked, command not run | 0 findings over an absent candidate diff | Unevaluated and retains the regex mechanism/root |
| 4 | try-fix-2 |
Proposed table-driven DFA only; no production diff | Blocked, command not run | 0 findings over an absent candidate diff | Unevaluated and substantially more complex |
No candidate failed the targeted tests with its fix applied. The two STEP 5a candidates rank below the implemented candidates because they were blocked before editing and have no regression result at all; the Gate failure on pr/pr-plus-reviewer is specifically a non-discriminating-test failure, not a with-fix test regression.
Why the raw PR wins
- It directly removes
System.Text.RegularExpressionsfromShadowTypeConverterrather than merely caching a regex. - Its bounded stack allocation and span scans preserve the old unanchored token extraction without introducing shared state.
- Its compatibility matrix exercises color alternatives, Unicode whitespace, invariant/scientific numbers, ignored punctuation, greedy hex matching, and incomplete-token rollback.
- The expert pass found no concrete parser, exception, analyzer-wiring, performance, or test-matrix defect.
- The DFA alternative is only a design sketch and would add disproportionate machinery for this small grammar.
Required change before approval
Add or identify a focused regression check for the actual objective—for example, a deterministic analyzer/build assertion that fails when Controls.Core references the banned regex namespace and passes at the submitted HEAD. Do not rely solely on behavioral compatibility tests, because preserving parser behavior necessarily lets those tests pass against the old regex implementation.
📱 UI Tests — Shadow
Detected UI test categories: Shadow
✅ Deep UI tests — 21 passed, 0 failed, 1 skipped across 1 category on platform-pool agent (replaces in-process counts above).
🧪 UI Test Execution Results (deep, platform pool)
| Category | Tests | Snapshot diffs |
|---|---|---|
Shadow |
21/22 (1 skipped) ✓ | — |
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs) |
🧭 Next Steps — review latest findings
No alternative fix was selected for this run. Review the session findings and CI results before merging.
<!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause `ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a shadow string. That reference contributes to retaining `System.Text.RegularExpressions` and additional regex implementation code in trimmed MAUI Android apps. ### Broader goal and related work This PR is one step toward removing the roughly 300 KB linked `System.Text.RegularExpressions` assembly from MAUI apps that do not use regular expressions in their own application logic. Removing the `ShadowTypeConverter` root is not sufficient by itself because other framework and dependency roots still retain regex code. Related work toward that goal includes: - #38616 trims disabled MAUI Aspire support, including `Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire feature switch is off. - dotnet/android#12813 avoids unnecessary XML marshalling references in trimmable type-map dispatch assemblies. This helps remove XML assemblies where they are not otherwise needed; `System.Private.Xml` contains a generated `LanguageRegex` implementation that can independently retain regex code. Additional roots may still need to be removed or made trimmable before `System.Text.RegularExpressions` disappears entirely from a given app. For example, the measured sample-content app also reaches regex through `CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`. ### Description of Change Replaces the runtime `Regex` with an allocation-conscious inline tokenizer/parser. The parser intentionally preserves the existing unanchored `Regex.Matches` behavior, including supported color forms, invariant-culture decimal/scientific numbers, Unicode whitespace, ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for incomplete decimals or exponents. Token boundaries are stored in a stack-allocated `Span<Part>`, and only matched tokens are materialized as strings for the existing color and numeric parsers. The project now also bans the `System.Text.RegularExpressions` namespace through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this dependency from being reintroduced. ### Performance considerations Vectorizing the character scans was considered but intentionally not implemented. Shadow strings are normally short (roughly tens of characters) and whitespace runs are generally zero to two characters, so SIMD setup and branching are unlikely to beat the straightforward scalar loops. A vectorized path would also require separate `netstandard` fallbacks and careful tables to preserve the old regex's Unicode `\s` and `\d` behavior, adding disproportionate complexity without demonstrated benefit. ### Tests - Added focused coverage for every supported color syntax, offsets/radius/opacity, whitespace, malformed values, invariant culture, and legacy regex edge cases. - `ShadowTests`: 85 passed. - `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and `net10.0`. - Verified an intentional `System.Text.RegularExpressions.Regex` usage in `Controls.Core` fails with RS0030. ### Android size measurement Published identical stock `.NET 11 preview 7` `dotnet new maui --sample-content` apps in Release with `TrimMode=partial`, replacing only the `Microsoft.Maui.Controls.dll` input in the patched build. | Artifact | Baseline | Patched | Delta | |---|---:|---:|---:| | Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** | | Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** | | Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B | 1,495,040 B | **-512 B** | | Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B | 330,752 B | **-3,584 B** | | arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** | | x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** | `System.Text.RegularExpressions` remains retained by other roots in this sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`), `System.Data.Common`, and `System.Private.Xml`. This change removes the `Microsoft.Maui.Controls` reference and reduces the linked regex implementation, but does not eliminate the assembly from this app. ### Issues Fixed None. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
## Summary - create XML marshalling metadata references lazily in generated `[Export]` dispatch assemblies - avoid adding `System.Xml.ReaderWriter` to a generated type-map assembly when exported signatures do not use XML parser mappings - preserve the existing XML marshalling references when `XmlPullParser` or `XmlResourceParser` mappings are used ## APK size and combined MAUI validation The PR is draft because the stock MAUI `sample-content` application does not currently demonstrate an APK-size or packaged-assembly reduction from this change. The first stock .NET 11 Android CoreCLR Release comparison produced an identical signed APK before and after this change: | Build | Signed APK size | |---|---:| | Baseline | 23,300,175 bytes | | Patched | 23,300,175 bytes | | Difference | **0 bytes** | Three independent MAUI trimming fixes were then applied together: - dotnet/maui#38613 trims unused runtime XAML loader/parser code - dotnet/maui#38615 removes `ShadowTypeConverter`'s regex dependency - dotnet/maui#38616 trims disabled MAUI Aspire support and its generated localhost regex The combined MAUI changes reduced the sample-content signed APK by 81,920 bytes, removed `XamlLoader`, `XamlParser`, and `LocalhostRegex`, and reduced the retained XML/regex implementations. However, `System.Xml.ReaderWriter` remains rooted directly by source-generated XAML line-info calls in the application, and `System.Private.Xml` remains transitively and through additional framework/control references. To exercise this PR's actual code path, the identical combined-MAUI input was then published twice with the trimmable Android type map explicitly enabled: ```sh dotnet publish CombinedTrimApp.csproj \ -c Release -f net11.0-android -r android-arm64 \ -p:AndroidKeyStore=false \ -p:PublishReadyToRun=false \ -p:TrimMode=partial \ -p:_AndroidTypeMapImplementation=trimmable \ -p:AndroidPackageFormats=apk ``` | Output | Baseline generator | Patched generator | Difference | |---|---:|---:|---:| | Signed APK | 21,248,079 B | 21,248,079 B | **0 B** | | Unsigned APK | 21,118,994 B | 21,119,021 B | +27 B packaging noise | | Generated type-map DLLs | 11,133,952 B | 11,133,952 B | **0 B** | | `System.Xml.ReaderWriter.dll` | 5,120 B | 5,120 B | **0 B** | | `System.Private.Xml.dll` | 381,440 B | 381,440 B | **0 B** | All 86 generated type-map DLLs were byte-identical, and none had a `System.Xml.ReaderWriter` assembly reference. The sample-content app and its dependencies do not contain a non-XML `[Export]` dispatcher that triggers the bug fixed here. Its remaining `System.Xml.ReaderWriter` reference comes from generated XAML `InitializeComponent` line-info services (`System.Xml.IXmlLineInfo`), outside the Android type-map generator. Therefore this change is valid generator cleanup and is covered by focused tests, but it does **not** currently reduce the stock MAUI sample-content APK or remove its XML assemblies. The benefit applies only to an app/library that generates a non-XML `[Export]` dispatcher and has no independent XML roots. ## Tests - `dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj --no-restore --nologo -v:minimal` - targeted `TypeMapAssemblyGeneratorTests.Generate_Export*` coverage
<!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause `ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a shadow string. That reference contributes to retaining `System.Text.RegularExpressions` and additional regex implementation code in trimmed MAUI Android apps. ### Broader goal and related work This PR is one step toward removing the roughly 300 KB linked `System.Text.RegularExpressions` assembly from MAUI apps that do not use regular expressions in their own application logic. Removing the `ShadowTypeConverter` root is not sufficient by itself because other framework and dependency roots still retain regex code. Related work toward that goal includes: - #38616 trims disabled MAUI Aspire support, including `Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire feature switch is off. - dotnet/android#12813 avoids unnecessary XML marshalling references in trimmable type-map dispatch assemblies. This helps remove XML assemblies where they are not otherwise needed; `System.Private.Xml` contains a generated `LanguageRegex` implementation that can independently retain regex code. Additional roots may still need to be removed or made trimmable before `System.Text.RegularExpressions` disappears entirely from a given app. For example, the measured sample-content app also reaches regex through `CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`. ### Description of Change Replaces the runtime `Regex` with an allocation-conscious inline tokenizer/parser. The parser intentionally preserves the existing unanchored `Regex.Matches` behavior, including supported color forms, invariant-culture decimal/scientific numbers, Unicode whitespace, ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for incomplete decimals or exponents. Token boundaries are stored in a stack-allocated `Span<Part>`, and only matched tokens are materialized as strings for the existing color and numeric parsers. The project now also bans the `System.Text.RegularExpressions` namespace through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this dependency from being reintroduced. ### Performance considerations Vectorizing the character scans was considered but intentionally not implemented. Shadow strings are normally short (roughly tens of characters) and whitespace runs are generally zero to two characters, so SIMD setup and branching are unlikely to beat the straightforward scalar loops. A vectorized path would also require separate `netstandard` fallbacks and careful tables to preserve the old regex's Unicode `\s` and `\d` behavior, adding disproportionate complexity without demonstrated benefit. ### Tests - Added focused coverage for every supported color syntax, offsets/radius/opacity, whitespace, malformed values, invariant culture, and legacy regex edge cases. - `ShadowTests`: 85 passed. - `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and `net10.0`. - Verified an intentional `System.Text.RegularExpressions.Regex` usage in `Controls.Core` fails with RS0030. ### Android size measurement Published identical stock `.NET 11 preview 7` `dotnet new maui --sample-content` apps in Release with `TrimMode=partial`, replacing only the `Microsoft.Maui.Controls.dll` input in the patched build. | Artifact | Baseline | Patched | Delta | |---|---:|---:|---:| | Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** | | Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** | | Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B | 1,495,040 B | **-512 B** | | Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B | 330,752 B | **-3,584 B** | | arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** | | x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** | `System.Text.RegularExpressions` remains retained by other roots in this sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`), `System.Data.Common`, and `System.Private.Xml`. This change removes the `Microsoft.Maui.Controls` reference and reduces the linked regex implementation, but does not eliminate the assembly from this app. ### Issues Fixed None. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
<!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause `ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a shadow string. That reference contributes to retaining `System.Text.RegularExpressions` and additional regex implementation code in trimmed MAUI Android apps. ### Broader goal and related work This PR is one step toward removing the roughly 300 KB linked `System.Text.RegularExpressions` assembly from MAUI apps that do not use regular expressions in their own application logic. Removing the `ShadowTypeConverter` root is not sufficient by itself because other framework and dependency roots still retain regex code. Related work toward that goal includes: - #38616 trims disabled MAUI Aspire support, including `Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire feature switch is off. - dotnet/android#12813 avoids unnecessary XML marshalling references in trimmable type-map dispatch assemblies. This helps remove XML assemblies where they are not otherwise needed; `System.Private.Xml` contains a generated `LanguageRegex` implementation that can independently retain regex code. Additional roots may still need to be removed or made trimmable before `System.Text.RegularExpressions` disappears entirely from a given app. For example, the measured sample-content app also reaches regex through `CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`. ### Description of Change Replaces the runtime `Regex` with an allocation-conscious inline tokenizer/parser. The parser intentionally preserves the existing unanchored `Regex.Matches` behavior, including supported color forms, invariant-culture decimal/scientific numbers, Unicode whitespace, ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for incomplete decimals or exponents. Token boundaries are stored in a stack-allocated `Span<Part>`, and only matched tokens are materialized as strings for the existing color and numeric parsers. The project now also bans the `System.Text.RegularExpressions` namespace through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this dependency from being reintroduced. ### Performance considerations Vectorizing the character scans was considered but intentionally not implemented. Shadow strings are normally short (roughly tens of characters) and whitespace runs are generally zero to two characters, so SIMD setup and branching are unlikely to beat the straightforward scalar loops. A vectorized path would also require separate `netstandard` fallbacks and careful tables to preserve the old regex's Unicode `\s` and `\d` behavior, adding disproportionate complexity without demonstrated benefit. ### Tests - Added focused coverage for every supported color syntax, offsets/radius/opacity, whitespace, malformed values, invariant culture, and legacy regex edge cases. - `ShadowTests`: 85 passed. - `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and `net10.0`. - Verified an intentional `System.Text.RegularExpressions.Regex` usage in `Controls.Core` fails with RS0030. ### Android size measurement Published identical stock `.NET 11 preview 7` `dotnet new maui --sample-content` apps in Release with `TrimMode=partial`, replacing only the `Microsoft.Maui.Controls.dll` input in the patched build. | Artifact | Baseline | Patched | Delta | |---|---:|---:|---:| | Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** | | Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** | | Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B | 1,495,040 B | **-512 B** | | Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B | 330,752 B | **-3,584 B** | | arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** | | x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** | `System.Text.RegularExpressions` remains retained by other roots in this sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`), `System.Data.Common`, and `System.Private.Xml`. This change removes the `Microsoft.Maui.Controls` reference and reduces the linked regex implementation, but does not eliminate the assembly from this app. ### Issues Fixed None. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
<!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause `ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a shadow string. That reference contributes to retaining `System.Text.RegularExpressions` and additional regex implementation code in trimmed MAUI Android apps. ### Broader goal and related work This PR is one step toward removing the roughly 300 KB linked `System.Text.RegularExpressions` assembly from MAUI apps that do not use regular expressions in their own application logic. Removing the `ShadowTypeConverter` root is not sufficient by itself because other framework and dependency roots still retain regex code. Related work toward that goal includes: - #38616 trims disabled MAUI Aspire support, including `Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire feature switch is off. - dotnet/android#12813 avoids unnecessary XML marshalling references in trimmable type-map dispatch assemblies. This helps remove XML assemblies where they are not otherwise needed; `System.Private.Xml` contains a generated `LanguageRegex` implementation that can independently retain regex code. Additional roots may still need to be removed or made trimmable before `System.Text.RegularExpressions` disappears entirely from a given app. For example, the measured sample-content app also reaches regex through `CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`. ### Description of Change Replaces the runtime `Regex` with an allocation-conscious inline tokenizer/parser. The parser intentionally preserves the existing unanchored `Regex.Matches` behavior, including supported color forms, invariant-culture decimal/scientific numbers, Unicode whitespace, ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for incomplete decimals or exponents. Token boundaries are stored in a stack-allocated `Span<Part>`, and only matched tokens are materialized as strings for the existing color and numeric parsers. The project now also bans the `System.Text.RegularExpressions` namespace through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this dependency from being reintroduced. ### Performance considerations Vectorizing the character scans was considered but intentionally not implemented. Shadow strings are normally short (roughly tens of characters) and whitespace runs are generally zero to two characters, so SIMD setup and branching are unlikely to beat the straightforward scalar loops. A vectorized path would also require separate `netstandard` fallbacks and careful tables to preserve the old regex's Unicode `\s` and `\d` behavior, adding disproportionate complexity without demonstrated benefit. ### Tests - Added focused coverage for every supported color syntax, offsets/radius/opacity, whitespace, malformed values, invariant culture, and legacy regex edge cases. - `ShadowTests`: 85 passed. - `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and `net10.0`. - Verified an intentional `System.Text.RegularExpressions.Regex` usage in `Controls.Core` fails with RS0030. ### Android size measurement Published identical stock `.NET 11 preview 7` `dotnet new maui --sample-content` apps in Release with `TrimMode=partial`, replacing only the `Microsoft.Maui.Controls.dll` input in the patched build. | Artifact | Baseline | Patched | Delta | |---|---:|---:|---:| | Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** | | Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** | | Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B | 1,495,040 B | **-512 B** | | Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B | 330,752 B | **-3,584 B** | | arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** | | x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** | `System.Text.RegularExpressions` remains retained by other roots in this sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`), `System.Data.Common`, and `System.Private.Xml`. This change removes the `Microsoft.Maui.Controls` reference and reduces the linked regex implementation, but does not eliminate the assembly from this app. ### Issues Fixed None. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
<!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause `ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a shadow string. That reference contributes to retaining `System.Text.RegularExpressions` and additional regex implementation code in trimmed MAUI Android apps. ### Broader goal and related work This PR is one step toward removing the roughly 300 KB linked `System.Text.RegularExpressions` assembly from MAUI apps that do not use regular expressions in their own application logic. Removing the `ShadowTypeConverter` root is not sufficient by itself because other framework and dependency roots still retain regex code. Related work toward that goal includes: - #38616 trims disabled MAUI Aspire support, including `Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire feature switch is off. - dotnet/android#12813 avoids unnecessary XML marshalling references in trimmable type-map dispatch assemblies. This helps remove XML assemblies where they are not otherwise needed; `System.Private.Xml` contains a generated `LanguageRegex` implementation that can independently retain regex code. Additional roots may still need to be removed or made trimmable before `System.Text.RegularExpressions` disappears entirely from a given app. For example, the measured sample-content app also reaches regex through `CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`. ### Description of Change Replaces the runtime `Regex` with an allocation-conscious inline tokenizer/parser. The parser intentionally preserves the existing unanchored `Regex.Matches` behavior, including supported color forms, invariant-culture decimal/scientific numbers, Unicode whitespace, ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for incomplete decimals or exponents. Token boundaries are stored in a stack-allocated `Span<Part>`, and only matched tokens are materialized as strings for the existing color and numeric parsers. The project now also bans the `System.Text.RegularExpressions` namespace through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this dependency from being reintroduced. ### Performance considerations Vectorizing the character scans was considered but intentionally not implemented. Shadow strings are normally short (roughly tens of characters) and whitespace runs are generally zero to two characters, so SIMD setup and branching are unlikely to beat the straightforward scalar loops. A vectorized path would also require separate `netstandard` fallbacks and careful tables to preserve the old regex's Unicode `\s` and `\d` behavior, adding disproportionate complexity without demonstrated benefit. ### Tests - Added focused coverage for every supported color syntax, offsets/radius/opacity, whitespace, malformed values, invariant culture, and legacy regex edge cases. - `ShadowTests`: 85 passed. - `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and `net10.0`. - Verified an intentional `System.Text.RegularExpressions.Regex` usage in `Controls.Core` fails with RS0030. ### Android size measurement Published identical stock `.NET 11 preview 7` `dotnet new maui --sample-content` apps in Release with `TrimMode=partial`, replacing only the `Microsoft.Maui.Controls.dll` input in the patched build. | Artifact | Baseline | Patched | Delta | |---|---:|---:|---:| | Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** | | Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** | | Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B | 1,495,040 B | **-512 B** | | Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B | 330,752 B | **-3,584 B** | | arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** | | x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** | `System.Text.RegularExpressions` remains retained by other roots in this sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`), `System.Data.Common`, and `System.Private.Xml`. This change removes the `Microsoft.Maui.Controls` reference and reduces the linked regex implementation, but does not eliminate the assembly from this app. ### Issues Fixed None. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
<!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause `ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a shadow string. That reference contributes to retaining `System.Text.RegularExpressions` and additional regex implementation code in trimmed MAUI Android apps. ### Broader goal and related work This PR is one step toward removing the roughly 300 KB linked `System.Text.RegularExpressions` assembly from MAUI apps that do not use regular expressions in their own application logic. Removing the `ShadowTypeConverter` root is not sufficient by itself because other framework and dependency roots still retain regex code. Related work toward that goal includes: - #38616 trims disabled MAUI Aspire support, including `Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire feature switch is off. - dotnet/android#12813 avoids unnecessary XML marshalling references in trimmable type-map dispatch assemblies. This helps remove XML assemblies where they are not otherwise needed; `System.Private.Xml` contains a generated `LanguageRegex` implementation that can independently retain regex code. Additional roots may still need to be removed or made trimmable before `System.Text.RegularExpressions` disappears entirely from a given app. For example, the measured sample-content app also reaches regex through `CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`. ### Description of Change Replaces the runtime `Regex` with an allocation-conscious inline tokenizer/parser. The parser intentionally preserves the existing unanchored `Regex.Matches` behavior, including supported color forms, invariant-culture decimal/scientific numbers, Unicode whitespace, ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for incomplete decimals or exponents. Token boundaries are stored in a stack-allocated `Span<Part>`, and only matched tokens are materialized as strings for the existing color and numeric parsers. The project now also bans the `System.Text.RegularExpressions` namespace through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this dependency from being reintroduced. ### Performance considerations Vectorizing the character scans was considered but intentionally not implemented. Shadow strings are normally short (roughly tens of characters) and whitespace runs are generally zero to two characters, so SIMD setup and branching are unlikely to beat the straightforward scalar loops. A vectorized path would also require separate `netstandard` fallbacks and careful tables to preserve the old regex's Unicode `\s` and `\d` behavior, adding disproportionate complexity without demonstrated benefit. ### Tests - Added focused coverage for every supported color syntax, offsets/radius/opacity, whitespace, malformed values, invariant culture, and legacy regex edge cases. - `ShadowTests`: 85 passed. - `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and `net10.0`. - Verified an intentional `System.Text.RegularExpressions.Regex` usage in `Controls.Core` fails with RS0030. ### Android size measurement Published identical stock `.NET 11 preview 7` `dotnet new maui --sample-content` apps in Release with `TrimMode=partial`, replacing only the `Microsoft.Maui.Controls.dll` input in the patched build. | Artifact | Baseline | Patched | Delta | |---|---:|---:|---:| | Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** | | Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** | | Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B | 1,495,040 B | **-512 B** | | Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B | 330,752 B | **-3,584 B** | | arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** | | x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** | `System.Text.RegularExpressions` remains retained by other roots in this sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`), `System.Data.Common`, and `System.Private.Xml`. This change removes the `Microsoft.Maui.Controls` reference and reduces the linked regex implementation, but does not eliminate the assembly from this app. ### Issues Fixed None. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
<!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause `ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a shadow string. That reference contributes to retaining `System.Text.RegularExpressions` and additional regex implementation code in trimmed MAUI Android apps. ### Broader goal and related work This PR is one step toward removing the roughly 300 KB linked `System.Text.RegularExpressions` assembly from MAUI apps that do not use regular expressions in their own application logic. Removing the `ShadowTypeConverter` root is not sufficient by itself because other framework and dependency roots still retain regex code. Related work toward that goal includes: - #38616 trims disabled MAUI Aspire support, including `Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire feature switch is off. - dotnet/android#12813 avoids unnecessary XML marshalling references in trimmable type-map dispatch assemblies. This helps remove XML assemblies where they are not otherwise needed; `System.Private.Xml` contains a generated `LanguageRegex` implementation that can independently retain regex code. Additional roots may still need to be removed or made trimmable before `System.Text.RegularExpressions` disappears entirely from a given app. For example, the measured sample-content app also reaches regex through `CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`. ### Description of Change Replaces the runtime `Regex` with an allocation-conscious inline tokenizer/parser. The parser intentionally preserves the existing unanchored `Regex.Matches` behavior, including supported color forms, invariant-culture decimal/scientific numbers, Unicode whitespace, ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for incomplete decimals or exponents. Token boundaries are stored in a stack-allocated `Span<Part>`, and only matched tokens are materialized as strings for the existing color and numeric parsers. The project now also bans the `System.Text.RegularExpressions` namespace through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this dependency from being reintroduced. ### Performance considerations Vectorizing the character scans was considered but intentionally not implemented. Shadow strings are normally short (roughly tens of characters) and whitespace runs are generally zero to two characters, so SIMD setup and branching are unlikely to beat the straightforward scalar loops. A vectorized path would also require separate `netstandard` fallbacks and careful tables to preserve the old regex's Unicode `\s` and `\d` behavior, adding disproportionate complexity without demonstrated benefit. ### Tests - Added focused coverage for every supported color syntax, offsets/radius/opacity, whitespace, malformed values, invariant culture, and legacy regex edge cases. - `ShadowTests`: 85 passed. - `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and `net10.0`. - Verified an intentional `System.Text.RegularExpressions.Regex` usage in `Controls.Core` fails with RS0030. ### Android size measurement Published identical stock `.NET 11 preview 7` `dotnet new maui --sample-content` apps in Release with `TrimMode=partial`, replacing only the `Microsoft.Maui.Controls.dll` input in the patched build. | Artifact | Baseline | Patched | Delta | |---|---:|---:|---:| | Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** | | Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** | | Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B | 1,495,040 B | **-512 B** | | Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B | 330,752 B | **-3,584 B** | | arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** | | x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** | `System.Text.RegularExpressions` remains retained by other roots in this sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`), `System.Data.Common`, and `System.Private.Xml`. This change removes the `Microsoft.Maui.Controls` reference and reduces the linked regex implementation, but does not eliminate the assembly from this app. ### Issues Fixed None. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

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!
Root Cause
ShadowTypeConverterconstructs a runtimeRegexwhenever it parses a shadow string. That reference contributes to retainingSystem.Text.RegularExpressionsand additional regex implementation code in trimmed MAUI Android apps.Broader goal and related work
This PR is one step toward removing the roughly 300 KB linked
System.Text.RegularExpressionsassembly from MAUI apps that do not use regular expressions in their own application logic. Removing theShadowTypeConverterroot is not sufficient by itself because other framework and dependency roots still retain regex code.Related work toward that goal includes:
Microsoft.Maui's source-generatedLocalhostRegex, when the Aspire feature switch is off.System.Private.Xmlcontains a generatedLanguageRegeximplementation that can independently retain regex code.Additional roots may still need to be removed or made trimmable before
System.Text.RegularExpressionsdisappears entirely from a given app. For example, the measured sample-content app also reaches regex throughCommunityToolkit.Maui,System.Data.Common, andSystem.Private.Xml.Description of Change
Replaces the runtime
Regexwith an allocation-conscious inline tokenizer/parser.The parser intentionally preserves the existing unanchored
Regex.Matchesbehavior, including supported color forms, invariant-culture decimal/scientific numbers, Unicode whitespace, ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for incomplete decimals or exponents.Token boundaries are stored in a stack-allocated
Span<Part>, and only matched tokens are materialized as strings for the existing color and numeric parsers.The project now also bans the
System.Text.RegularExpressionsnamespace through aControls.Core-specificBannedSymbols.txt, preventing this dependency from being reintroduced.Performance considerations
Vectorizing the character scans was considered but intentionally not implemented. Shadow strings are normally short (roughly tens of characters) and whitespace runs are generally zero to two characters, so SIMD setup and branching are unlikely to beat the straightforward scalar loops. A vectorized path would also require separate
netstandardfallbacks and careful tables to preserve the old regex's Unicode\sand\dbehavior, adding disproportionate complexity without demonstrated benefit.Tests
ShadowTests: 85 passed.Controls.Corebuilds fornetstandard2.0,netstandard2.1, andnet10.0.System.Text.RegularExpressions.Regexusage inControls.Corefails with RS0030.Android size measurement
Published identical stock
.NET 11 preview 7dotnet new maui --sample-contentapps in Release withTrimMode=partial, replacing only theMicrosoft.Maui.Controls.dllinput in the patched build.Microsoft.Maui.Controls.dll(per ABI)System.Text.RegularExpressions.dll(per ABI)System.Text.RegularExpressionsremains retained by other roots in this sample:CommunityToolkit.Maui,Microsoft.Maui(LocalhostRegex),System.Data.Common, andSystem.Private.Xml. This change removes theMicrosoft.Maui.Controlsreference and reduces the linked regex implementation, but does not eliminate the assembly from this app.Issues Fixed
None.