Repository navigation
Improve XEXPR diagnostics for interpolated strings and lambda method groups - #35093
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35093Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35093" |
There was a problem hiding this comment.
Pull request overview
This PR improves .NET MAUI XAML C# expression (XEXPR) source-generator diagnostics so that certain invalid expressions fail with MAUIX* diagnostics instead of falling through to cryptic C# compiler errors.
Changes:
- Added Roslyn-based extraction of identifiers from interpolated string holes to report MAUIX2009 at source generation time.
- Added a new MAUIX2017 diagnostic for event-handler lambdas whose body is a method group reference (missing invocation).
- Added unit tests and registered the new diagnostic in analyzer release notes.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Controls/tests/SourceGen.UnitTests/CSharpExpressionDiagnosticsTests.cs | Adds coverage for interpolated-string missing member and lambda method-group diagnostics. |
| src/Controls/src/SourceGen/SetPropertyHelpers.cs | Emits MAUIX2009 for missing members inside interpolated strings; emits MAUIX2017 for method-group lambda bodies in ConnectEvent. |
| src/Controls/src/SourceGen/Descriptors.cs | Introduces MAUIX2017 descriptor definition. |
| src/Controls/src/SourceGen/CSharpExpressionHelpers.cs | Adds helper methods to parse interpolated strings and detect method-group lambda bodies. |
| src/Controls/src/SourceGen/AnalyzerReleases.Unshipped.md | Registers MAUIX2017 in the unshipped analyzer release list. |
| // Validate identifiers inside interpolated string holes | ||
| if (expression.Code.StartsWith("$\"", StringComparison.Ordinal) || expression.Code.StartsWith("$@\"", StringComparison.Ordinal)) | ||
| { | ||
| var interpolatedIds = CSharpExpressionHelpers.ExtractInterpolatedStringIdentifiers(expression.Code); | ||
| foreach (var id in interpolatedIds) | ||
| { | ||
| var idResolution = MemberResolver.Resolve(id, context.RootType, dataTypeSymbol, context.Compilation); | ||
| if (idResolution.Location == MemberLocation.Neither) | ||
| { | ||
| var idLocation = LocationCreate(context.ProjectItem.RelativePath!, (IXmlLineInfo)valueNode, expression.Code); | ||
| context.ReportDiagnostic(Diagnostic.Create(Descriptors.MemberNotFound, idLocation, id, context.RootType?.Name ?? "this", dataTypeSymbol.Name)); | ||
| return true; // Handled (with error) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The interpolated-string identifier validation will incorrectly report MAUIX2009 for identifiers that are not members on the page/ViewModel but are still valid C# in interpolation holes (e.g., DateTime.Now, System.String, Math.PI). In those cases MemberResolver.Resolve(id, ...) returns Neither and this code turns otherwise-valid expressions into generator errors. Consider skipping identifiers that resolve to a type/namespace (via the Compilation) or performing semantic analysis on each interpolation hole so only actual member references against this/x:DataType are validated.
| if (expression.Code.StartsWith("$\"", StringComparison.Ordinal) || expression.Code.StartsWith("$@\"", StringComparison.Ordinal)) | ||
| { | ||
| var interpolatedIds = CSharpExpressionHelpers.ExtractInterpolatedStringIdentifiers(expression.Code); |
There was a problem hiding this comment.
This interpolated-string check only triggers for expression.Code starting with $" or $@". Valid interpolated strings can also start with @$" (and may have leading whitespace), so those cases will skip identifier validation and still fall through to C# compilation errors. Consider using TrimStart() plus handling @$", or (more robust) detecting interpolated-string syntax via Roslyn rather than string prefix checks.
| if (expression.Code.StartsWith("$\"", StringComparison.Ordinal) || expression.Code.StartsWith("$@\"", StringComparison.Ordinal)) | |
| { | |
| var interpolatedIds = CSharpExpressionHelpers.ExtractInterpolatedStringIdentifiers(expression.Code); | |
| var trimmedExpressionCode = expression.Code.TrimStart(); | |
| if (trimmedExpressionCode.StartsWith("$\"", StringComparison.Ordinal) || | |
| trimmedExpressionCode.StartsWith("$@\"", StringComparison.Ordinal) || | |
| trimmedExpressionCode.StartsWith("@$\"", StringComparison.Ordinal)) | |
| { | |
| var interpolatedIds = CSharpExpressionHelpers.ExtractInterpolatedStringIdentifiers(trimmedExpressionCode); |
| var parameters = string.Join(", ", lambda.ParameterList.Parameters.Select(p => p.Identifier.Text)); | ||
| return (memberAccess.ToString(), parameters); | ||
| } | ||
|
|
||
| // Also check for bare identifier (e.g., (s, e) => OnClicked) | ||
| if (body is IdentifierNameSyntax identifier) | ||
| { | ||
| var parameters = string.Join(", ", lambda.ParameterList.Parameters.Select(p => p.Identifier.Text)); | ||
| return (identifier.Identifier.Text, parameters); | ||
| } | ||
|
|
||
| return null; | ||
| } |
There was a problem hiding this comment.
DetectLambdaMethodGroupReference builds the suggested invocation argument list from the lambda parameter identifiers. If the lambda uses discard parameters (e.g. (_, _) => this.OnClicked) the diagnostic will suggest this.OnClicked(_, _), which does not compile because discards can’t be referenced. Consider omitting parameter names when any parameter is _, or generating safe placeholders (e.g. arg0, arg1) for the message.
| var parameters = string.Join(", ", lambda.ParameterList.Parameters.Select(p => p.Identifier.Text)); | |
| return (memberAccess.ToString(), parameters); | |
| } | |
| // Also check for bare identifier (e.g., (s, e) => OnClicked) | |
| if (body is IdentifierNameSyntax identifier) | |
| { | |
| var parameters = string.Join(", ", lambda.ParameterList.Parameters.Select(p => p.Identifier.Text)); | |
| return (identifier.Identifier.Text, parameters); | |
| } | |
| return null; | |
| } | |
| var parameters = GetLambdaInvocationArgumentList(lambda.ParameterList.Parameters); | |
| return (memberAccess.ToString(), parameters); | |
| } | |
| // Also check for bare identifier (e.g., (s, e) => OnClicked) | |
| if (body is IdentifierNameSyntax identifier) | |
| { | |
| var parameters = GetLambdaInvocationArgumentList(lambda.ParameterList.Parameters); | |
| return (identifier.Identifier.Text, parameters); | |
| } | |
| return null; | |
| } | |
| static string GetLambdaInvocationArgumentList(SeparatedSyntaxList<ParameterSyntax> parameters) | |
| { | |
| var parameterNames = parameters.Select(p => p.Identifier.Text).ToArray(); | |
| if (parameterNames.Any(name => name == "_")) | |
| return string.Join(", ", parameterNames.Select((_, index) => $"arg{index}")); | |
| return string.Join(", ", parameterNames); | |
| } |
| Assert.Contains(result.Diagnostics, d => d.Id == "MAUIX2009"); | ||
| } | ||
|
|
||
| [Fact] |
There was a problem hiding this comment.
The new interpolated-string validation adds a new failure mode (false positives on static types/namespaces like DateTime.Now / System.String). Consider adding a unit test that asserts no MAUIX2009 is reported for an interpolated string that uses a static type in an interpolation hole, to prevent regressions once the validation logic is adjusted.
| [Fact] | |
| [Fact] | |
| public void InterpolatedStaticTypeReference_DoesNotReportMAUIX2009() | |
| { | |
| var xaml = | |
| """ | |
| <?xml version="1.0" encoding="utf-8" ?> | |
| <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | |
| xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | |
| xmlns:sys="clr-namespace:System;assembly=netstandard" | |
| x:Class="TestApp.InterpolatedStaticTypePage"> | |
| <Label Text="{Binding $`Now: {sys:DateTime.Now}`}" /> | |
| </ContentPage> | |
| """; | |
| var codeBehind = | |
| """ | |
| using Microsoft.Maui.Controls; | |
| using Microsoft.Maui.Controls.Xaml; | |
| namespace TestApp; | |
| [XamlProcessing(XamlInflator.SourceGen)] | |
| public partial class InterpolatedStaticTypePage : ContentPage | |
| { | |
| public InterpolatedStaticTypePage() => InitializeComponent(); | |
| } | |
| """; | |
| var (result, _) = RunGenerator(xaml, codeBehind, assertNoCompilationErrors: false); | |
| Assert.DoesNotContain(result.Diagnostics, d => d.Id == "MAUIX2009"); | |
| } | |
| [Fact] |
|
/review -b feature/refactor-copilot-yml |
This comment has been minimized.
This comment has been minimized.
kubaflo
left a comment
There was a problem hiding this comment.
Could you please resolve conflicts?
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 4 findings
See inline comments for details.
| var interpolatedIds = CSharpExpressionHelpers.ExtractInterpolatedStringIdentifiers(expression.Code); | ||
| foreach (var id in interpolatedIds) | ||
| { | ||
| var idResolution = MemberResolver.Resolve(id, context.RootType, dataTypeSymbol, context.Compilation); |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[major] XAML & Bindings / Logic and Correctness — Resolving every interpolated-string hole root as a page or x:DataType member reports MAUIX2009 for valid static type expressions. For example, a hole like {DateTime.Now} is extracted as DateTime, which is neither a page member nor a view-model member, so this branch incorrectly rejects valid C# interpolation instead of allowing the later generated expression to compile.
| var root = tree.GetRoot(); | ||
|
|
||
| // Find lambda expressions | ||
| var lambda = root.DescendantNodes().OfType<ParenthesizedLambdaExpressionSyntax>().FirstOrDefault(); |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[major] XAML & Bindings / Diagnostic Accuracy — This only inspects ParenthesizedLambdaExpressionSyntax, so single-parameter lambdas parse as SimpleLambdaExpressionSyntax and bypass MAUIX2017. For a one-parameter event delegate, an expression like {e => OnEvent} will still fall through to generated C# compiler errors instead of the intended source-generator diagnostic.
| return null; | ||
|
|
||
| // Check if the body is a member access (this.Method or just Method) without invocation | ||
| if (body is MemberAccessExpressionSyntax memberAccess) |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[major] XAML & Bindings / Logic and Correctness — Treating every member-access lambda body as a method-group reference is syntax-only and rejects valid non-void event lambdas. For an event delegate that returns a value, {(s, e) => this.Title} can be a valid property-returning lambda, but this reports MAUIX2017 because it never checks whether the member is actually a method group or whether the event return type is void.
| isEnabledByDefault: true); | ||
|
|
||
| // x:Code diagnostics | ||
| public static DiagnosticDescriptor XCodeNotChildOfRoot = new DiagnosticDescriptor( |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[moderate] Public API Surface / Regression Prevention — MAUIX2015 and MAUIX2016 are added as descriptors and release-tracked, but there is no emission path for either diagnostic in the source generator. This advertises analyzer rules that can never be produced; either add the x:Code validation that reports them or remove the descriptors and release-tracking entries from this PR.
Suppress MAUIX2009 for static type references inside interpolated string holes and avoid invalid MAUIX2017 suggestions for discard lambda parameters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| } | ||
|
|
||
| // Validate identifiers inside interpolated string holes | ||
| if (expression.Code.StartsWith("$\"", StringComparison.Ordinal) || expression.Code.StartsWith("$@\"", StringComparison.Ordinal)) |
| // Find lambda expressions | ||
| var lambda = root.DescendantNodes().OfType<ParenthesizedLambdaExpressionSyntax>().FirstOrDefault(); | ||
| if (lambda == null) | ||
| return null; | ||
|
|
||
| // Get the lambda body - should be an expression (not a block) | ||
| var body = lambda.ExpressionBody; | ||
| if (body == null) | ||
| return null; | ||
|
|
||
| // Check if the body is a member access (this.Method or just Method) without invocation | ||
| if (body is MemberAccessExpressionSyntax memberAccess) | ||
| { | ||
| // Check that the parent is NOT an InvocationExpression | ||
| // (If it were "this.Method()", the body would be InvocationExpressionSyntax, not MemberAccessExpressionSyntax) | ||
| var methodGroup = memberAccess.ToString(); | ||
| return (methodGroup, CreateLambdaMethodGroupSuggestion(methodGroup, lambda.ParameterList.Parameters)); | ||
| } | ||
|
|
||
| // Also check for bare identifier (e.g., (s, e) => OnClicked) | ||
| if (body is IdentifierNameSyntax identifier) | ||
| { | ||
| var methodGroup = identifier.Identifier.Text; | ||
| return (methodGroup, CreateLambdaMethodGroupSuggestion(methodGroup, lambda.ParameterList.Parameters)); | ||
| } |
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 5 findings
See inline comments for details.
| AddInterpolatedIdentifier(idName.Identifier.Text, idName.Identifier.Text, identifiers, seen); | ||
| break; | ||
| case MemberAccessExpressionSyntax memberAccess: | ||
| AddMemberAccessRoot(memberAccess, identifiers, seen); |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[major] XAML & Bindings — This records only the root identifier for member-access interpolation holes. Concrete scenario: Text="{$'{User.MissingName}'}" where User exists on the x:DataType but MissingName does not; validation only sees User, so no MAUIX2009 is reported and source gen still falls through to a generated C# error. Please keep enough of the member chain to validate nested members, not just the root.
| if (idResolution.Location == MemberLocation.Neither && | ||
| !MemberResolver.HasMember(context.RootType, id, includeMethods: true) && | ||
| !MemberResolver.HasMember(dataTypeSymbol, id, includeMethods: true) && | ||
| !MemberResolver.ResolvesToType(context.Compilation, idReference.TypeReferenceCandidate, MemberResolver.GetContainingNamespace(context.RootType))) |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[major] XAML & Bindings — Type-reference suppression does not account for using aliases. Concrete scenario: code-behind has using DT = System.DateTime; and XAML uses Text="{$'{DT.Now}'}"; TypeReferenceCandidate is DT, ResolvesToType cannot resolve aliases, and MAUIX2009 is reported even though the expression is valid C#. This needs semantic alias resolution (or equivalent alias collection) before reporting member-not-found.
| if (vn.Value is Expression expression) | ||
| { | ||
| // Check for method group reference in lambda body (missing parentheses) | ||
| var methodGroupRef = CSharpExpressionHelpers.DetectLambdaMethodGroupReference(expression.Code); |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[major] Logic and Correctness — The new method-group diagnostic runs syntactically before considering the event delegate type, which rejects valid lambda bodies for non-void delegate-returning events. For example, a custom control can expose event Func<Action>? Produce; and Produce="{() => this.OnProduced}" is valid C# (the lambda returns a method group convertible to Action), but this path now reports MAUIX2017. Please only report this when semantic conversion to the actual event handler type fails.
| var root = tree.GetRoot(); | ||
|
|
||
| // Find lambda expressions | ||
| var lambda = root.DescendantNodes().OfType<ParenthesizedLambdaExpressionSyntax>().FirstOrDefault(); |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[moderate] XAML & Bindings — MAUIX2017 only inspects ParenthesizedLambdaExpressionSyntax, so simple lambdas still fall through to compiler errors. Concrete scenario: a custom one-argument event such as event Action<object>? Tapped; with Tapped="{s => this.OnTapped}" has the same missing-invocation bug, but this method returns null and source gen emits invalid C# instead of the new diagnostic. Include SimpleLambdaExpressionSyntax in the check.
| MAUIX2011 | XamlParsing | Warning | AmbiguousMemberWithStaticType | ||
| MAUIX2012 | XamlParsing | Error | CSharpExpressionsRequirePreviewFeatures | ||
| MAUIX2013 | XamlParsing | Error | AsyncLambdaNotSupported | ||
| MAUIX2015 | XamlParsing | Error | XCodeNotChildOfRoot |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[moderate] Build & MSBuild — MAUIX2015 and MAUIX2016 are added to the analyzer release table, but the current source only defines their descriptors and never reports them (searching the source-gen project finds no emission path or tests). This publishes unshipped analyzer IDs that users cannot actually receive; either wire the x:Code validation/reporting in this PR or remove these release entries until the diagnostics are implemented.
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
@StephaneDelcroix — new AI review results are available based on this last commit:
c937081. To request a fresh review after new comments or commits, comment/review rerun.
🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix
Gate Result: ✅ PASSED
Platform: ANDROID · Base: main · Merge base: dab8abc4
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🧪 CSharpExpressionDiagnosticsTests CSharpExpressionDiagnosticsTests |
🛠️ BUILD ERROR | ✅ PASS — 25s |
🔴 Without fix — 🧪 CSharpExpressionDiagnosticsTests: 🛠️ BUILD ERROR · 29s
Determining projects to restore...
Restored /home/vsts/work/1/s/src/Controls/tests/SourceGen.UnitTests/SourceGen.UnitTests.csproj (in 4.31 sec).
Restored /home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 4.34 sec).
7 of 9 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14587652
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14587652
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14587652
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14587652
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14587652
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14587652
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0/Microsoft.Maui.Controls.Xaml.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14587652
CSC : error RS2002: Rule 'MAUIX2015' is part of the next unshipped analyzer release, but is not a supported diagnostic for any analyzer (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md) [/home/vsts/work/1/s/src/Controls/src/SourceGen/Controls.SourceGen.csproj]
CSC : error RS2002: Rule 'MAUIX2016' is part of the next unshipped analyzer release, but is not a supported diagnostic for any analyzer (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md) [/home/vsts/work/1/s/src/Controls/src/SourceGen/Controls.SourceGen.csproj]
CSC : error RS2002: Rule 'MAUIX2017' is part of the next unshipped analyzer release, but is not a supported diagnostic for any analyzer (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md) [/home/vsts/work/1/s/src/Controls/src/SourceGen/Controls.SourceGen.csproj]
🟢 With fix — 🧪 CSharpExpressionDiagnosticsTests: PASS ✅ · 25s
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14587652
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14587652
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14587652
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14587652
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14587652
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14587652
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0/Microsoft.Maui.Controls.Xaml.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14587652
Controls.SourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.SourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.SourceGen.dll
SourceGen.UnitTests -> /home/vsts/work/1/s/artifacts/bin/SourceGen.UnitTests/Debug/net10.0/Microsoft.Maui.Controls.SourceGen.UnitTests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/SourceGen.UnitTests/Debug/net10.0/Microsoft.Maui.Controls.SourceGen.UnitTests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.31] Discovering: Microsoft.Maui.Controls.SourceGen.UnitTests
[xUnit.net 00:00:00.47] Discovered: Microsoft.Maui.Controls.SourceGen.UnitTests
[xUnit.net 00:00:00.48] Starting: Microsoft.Maui.Controls.SourceGen.UnitTests
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.LambdaEventHandler_MethodGroupWithoutInvocation_ShouldReportDiagnostic [2 s]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.SimplePropertyBinding_OnTwoWayProperty_NoWarning [979 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.SingleQuotedLiteral_MethodExpectsChar_GeneratesCharLiteral [21 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.InterpolatedString_StaticTypeReferences_ShouldNotReportMemberNotFound [171 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.BareIdentifier_ExplicitMarkupPrefix_NoAmbiguityWarning [37 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.MemberConflictsWithStaticType_ReportsMAUIX2011 [40 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.OperatorAliases_ANDandOR_TransformedToLogicalOperators [49 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.SyncLambdaEventHandler_NoError [36 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.NullConditionalAccess_OnTwoWayProperty_IsSettable [13 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.MemberNotFound_ReportsMAUIX2009 [27 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.LambdaEventHandler_MethodGroupWithDiscardParameters_ShouldOmitSuggestion [11 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.AsyncLambdaEventHandler_ReportsMAUIX2013 [23 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.AmbiguousMember_ReportsMAUIX2008 [26 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.ExplicitDotPrefix_NoStaticTypeConflictWarning [31 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.InterpolatedString_BinaryHoleWithMissingMember_ShouldReportDiagnostic [12 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.CSharpExpression_WithoutPreviewFeatures_ReportsMAUIX2012 [18 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.BareIdentifier_AmbiguousBetweenMarkupAndProperty_ReportsMAUIX2007 [50 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.ExplicitThisPrefix_NoAmbiguousError [31 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.ComplexExpression_OnTwoWayProperty_EmitsInfoDiagnostic [16 ms]
[xUnit.net 00:00:04.95] Finished: Microsoft.Maui.Controls.SourceGen.UnitTests
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.SingleQuotedLiteral_MethodExpectsString_GeneratesStringLiteral [31 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.BareIdentifier_ExplicitExpressionSyntax_NoAmbiguityWarning [28 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.ExplicitDotPrefix_NoAmbiguousError [28 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.InterpolatedString_BinaryAndConditionalHoles_ShouldNotReportMemberNotFound [34 ms]
Passed Microsoft.Maui.Controls.SourceGen.UnitTests.CSharpExpressionDiagnosticsTests.InterpolatedString_NonExistentMember_ShouldReportDiagnostic [13 ms]
Test Run Successful.
Total tests: 24
Passed: 24
Total time: 6.3482 Seconds
⚠️ Failure Details
- 🛠️ CSharpExpressionDiagnosticsTests without fix: build failed before tests could run
CSC : error RS2002: Rule 'MAUIX2015' is part of the next unshipped analyzer release, but is not a supported diagnostic for any analyzer (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Micros...
📁 Fix files reverted (5 files)
src/Controls/src/SourceGen/CSharpExpressionHelpers.cssrc/Controls/src/SourceGen/Descriptors.cssrc/Controls/src/SourceGen/ExpressionAnalyzer.cssrc/Controls/src/SourceGen/MemberResolver.cssrc/Controls/src/SourceGen/SetPropertyHelpers.cs
🔗 Regression Cross-Reference
🔍 Regression Cross-Reference
⚠ Overlaps with prior bug-fix PRs — same files modified, but no exact line revert detected.
| File | Fix PR | Fixed issue(s) |
|---|---|---|
src/Controls/src/SourceGen/SetPropertyHelpers.cs |
#34727 | #34726 |
src/Controls/src/SourceGen/SetPropertyHelpers.cs |
#34137 | #34075 |
🧪 Regression Tests to Verify
These tests were added by the overlapping fix PRs. Running them to verify no side-effect regressions:
| Fix PR | Type | Test | Filter |
|---|---|---|---|
| #34727 | XamlUnitTest | Maui34726 | Maui34726 |
| #34137 | XamlUnitTest | Maui34075ViewModel | Maui34075ViewModel |
🧪 Regression Test Results
✅ PASSED — 2 passed, 0 failed, 0 skipped
| Fix PR | Test | Type | Result |
|---|---|---|---|
| #34727 | Maui34726 | XamlUnitTest | ✅ PASSED |
| #34137 | Maui34075ViewModel | XamlUnitTest | ✅ PASSED |
📋 Pre-Flight — Context & Validation
Issue: N/A - PR description covers the bug scenarios directly
PR: #35093 - Improve XEXPR diagnostics for interpolated strings and lambda method groups
Platforms Affected: SourceGen / XAML build-time diagnostics; test platform requested: android
Files Changed: 6 implementation, 1 test
Key Findings
ghis unauthenticated, so PR metadata/comments were gathered from the public GitHub API and localorigin/main...HEADdiff.- The PR improves diagnostics for missing members in interpolated strings and lambda method-group event handlers, but the current implementation still misses
@$interpolation, simple lambdas, nested missing members, and some static type forms. - The PR description is stale relative to the checked-out diff and does not explain
MAUIX2015/MAUIX2016descriptor/release entries.
Code Review Summary
Verdict: NEEDS_CHANGES
Confidence: low
Errors: 3 | Warnings: 2 | Suggestions: 0
Key code review findings:
- ✗
SetPropertyHelpers.cs: interpolated static chains such asDateTime.Now.Dayand aliases such asstring.Emptycan still false-positive asMAUIX2009. - ✗
CSharpExpressionHelpers.cs/SetPropertyHelpers.cs: nested missing members such asUser.MissingNameare not validated. - ✗
CSharpExpressionHelpers.cs/SetPropertyHelpers.cs: method-group lambda detection is syntax-only and can reject valid non-void delegate lambdas. - ⚠
CSharpExpressionHelpers.cs:SimpleLambdaExpressionSyntaxis not checked. - ⚠
Descriptors.cs/AnalyzerReleases.Unshipped.md:MAUIX2015andMAUIX2016appear to be release-tracked without an emission path.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #35093 | Manual syntax extraction for interpolated holes plus syntax-only parenthesized lambda method-group detection | ✅ PASSED (Gate) | CSharpExpressionHelpers.cs, SetPropertyHelpers.cs, Descriptors.cs, ExpressionAnalyzer.cs, MemberResolver.cs, AnalyzerReleases.Unshipped.md, tests |
Original PR |
🔬 Code Review — Deep Analysis
Code Review — PR #35093
Independent Assessment
What this changes: Improves XAML C# expression source-generator diagnostics. The PR reports MAUIX2009 for missing identifiers inside interpolated-string holes and adds MAUIX2017 for event-handler lambdas that reference a method group without invoking it.
Inferred motivation: Replace confusing generated-C# compiler failures with source-generator diagnostics at the XAML location.
Reconciliation with PR Narrative
Author claims: The PR fixes two XEXPR cases: missing members inside interpolated strings and lambda event handlers missing ().
Agreement/disagreement: The checked-out diff matches the broad goal, but the PR description is stale. It omits later changes in ExpressionAnalyzer.cs and MemberResolver.cs, additional tests, and unrelated-looking MAUIX2015 / MAUIX2016 descriptor/release entries.
Prior Review Reconciliation
| Prior finding | Source | Status | Evidence |
|---|---|---|---|
Static type interpolation false positives, e.g. DateTime.Now |
Copilot/MauiBot inline comments | Partially fixed | Current PR added type-reference checks and static-type tests, but deeper chains and aliases remain risky. |
@$"..." interpolated strings skip validation |
Copilot inline comment | Unresolved | SetPropertyHelpers.cs only checks StartsWith("$\"") and StartsWith("$@\""). |
Discard parameters suggested as (_, _) |
Copilot inline comment | Fixed | Current helper omits the suggestion when any lambda parameter is _; test added. |
Single-parameter lambdas bypass MAUIX2017 |
Copilot/MauiBot inline comments | Unresolved | Current helper searches only ParenthesizedLambdaExpressionSyntax. |
| Syntax-only method-group detection can reject valid non-void lambdas | MauiBot inline comment | Unresolved | ConnectEvent() does not inspect the event delegate return type before reporting MAUIX2017. |
MAUIX2015 / MAUIX2016 descriptors added without emission paths |
MauiBot inline comment | Unresolved | Local search found descriptors/release entries but no report path. |
Blast Radius Assessment
- Runs for all instances: no runtime path; build-time source generator only.
- Startup impact: no.
- Static/shared state: no mutable state; new static helpers/descriptors only.
CI Status
- Required-check result: undetermined.
- Classification:
ghis unauthenticated in this environment; public API did not provide an authoritative required-check result. - Action taken: confidence capped to low for review verdict purposes.
Findings
❌ Error — Static type validation remains too shallow
SetPropertyHelpers.cs validates interpolation roots using TypeReferenceCandidate, but for valid expressions such as DateTime.Now.Day the candidate can become DateTime.Now, which is not a type. C# aliases such as string.Empty are also not mapped to metadata type names.
❌ Error — Nested missing members inside interpolation holes are not diagnosed
The current extractor checks only the root identifier. If User exists but User.MissingName does not, validation accepts the expression and generated C# can still fail later.
❌ Error — Lambda method-group diagnostic is syntax-only and can reject valid non-void event lambdas
(s, e) => this.Title is invalid for void event delegates but can be valid for a non-void custom delegate. The current diagnostic reports before checking the event delegate return type.
⚠️ Warning — Single-parameter lambdas bypass MAUIX2017
s => this.OnChanged parses as SimpleLambdaExpressionSyntax, but the PR only checks parenthesized lambdas.
⚠️ Warning — MAUIX2015 and MAUIX2016 appear unrelated/unemitted
Descriptors and release entries are present, but no source-generator emission path was found in the PR diff.
Failure-Mode Probing
- Valid static chain (
{$'{DateTime.Now.Day}'}): risks falseMAUIX2009. - Valid alias type (
{$'{string.Empty}'}): risks falseMAUIX2009. - Nested missing property (
{$'{User.Missing}'}): likely falls through instead of reportingMAUIX2009. - Alternate interpolation prefix (
{= @$"Hello {Missing}"}): skips validation. - Custom non-void event (
{(s, e) => this.Title}): can be rejected even when valid. - Single-param event lambda (
{e => this.OnEvent}): missesMAUIX2017.
Verdict: NEEDS_CHANGES
Confidence: low
Summary: The PR is directionally correct and gate-passing, but the current fix still has high-confidence diagnostic correctness gaps. The best alternative explored in try-fix addresses these gaps while preserving the PR's public diagnostic behavior.
🛠️ Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix | Unified Roslyn SyntaxWalker over interpolation holes plus symbolic member-chain validation and delegate-aware lambda checks |
✅ PASS | 3 files | Passes primary SourceGen diagnostics and mandatory XAML regressions; addresses known gaps in PR fix |
| PR | PR #35093 | Root identifier extraction for interpolated strings plus syntax-only parenthesized lambda method-group detection | ✅ PASSED (Gate) | 7 files | Original PR |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 1 | Yes | Semantic mini-compilation validation. Rejected before implementation due source-generator performance/invasiveness risk. |
| claude-opus-4.7 | 1 | Yes | Unified syntax walker plus symbolic chain validation. Implemented as Candidate #1. |
Exhausted: Yes
Selected Fix: Candidate #1 — It passed all required tests and is more robust than the PR's current fix for static chains, aliases, nested missing members, simple lambdas, and non-void event delegates.
📝 Recommended PR Title & Description
Assessment: ✏️ Recommend updating — the current description is detailed but stale for the winning pr-plus-reviewer fix because it omits nested interpolation member-chain validation, simple-lambda coverage, delegate return-type gating, alias/static type handling, and removal of unrelated MAUIX2015/MAUIX2016 metadata.
Recommended title
SourceGen XEXPR: Improve diagnostics for interpolated strings and lambda method groups
Recommended description
## Description
Two XAML C# expression (XEXPR) scenarios currently produce C# compilation errors instead of source-generator diagnostics, making errors harder to understand and fix. This PR catches both at source generation time with clear messages.
### Issue 1: Interpolated string with nonexistent member
```xml
<Label Text="{$'Hello, {Name1}!'}" />
When Name1 doesn't exist on the page or ViewModel, source gen previously emitted $"Hello, {__source.Name1}!" verbatim, resulting in a cryptic C# compilation error. Now MAUIX2009 is reported: "'Name1' not found on 'MyPage' or 'MyViewModel'."
Root cause: The existing MAUIX2009 check only ran for simple identifiers (IsSimpleIdentifier returns false for interpolated strings due to $, {, } chars). The fix parses interpolation holes with Roslyn and validates referenced members against MemberResolver.
Additional coverage: Interpolation validation now preserves member chains so nested missing members such as User.MissingName are reported instead of only validating the root User. It also avoids false positives for static type references, C# aliases such as string, using aliases such as using DT = System.DateTime;, and alternate interpolated string syntaxes that parse as InterpolatedStringExpressionSyntax.
Issue 2: Lambda with method group instead of invocation
<Button Clicked="{(s, e) => this.OnCounterClicked}" />Missing () caused source gen to emit button.Clicked += (s, e) => this.OnCounterClicked; — invalid C# for void-returning event handlers because a method group can't be the expression body of a void-returning lambda. Now MAUIX2017 is reported: "Lambda body 'this.OnCounterClicked' is a method group reference, not a method invocation. Did you mean 'this.OnCounterClicked(s, e)'?"
Root cause: ConnectEvent() emitted lambda expressions with zero validation. The fix parses lambda bodies with Roslyn to detect member-access or identifier method-group references without invocation.
Additional coverage: The diagnostic covers both parenthesized and simple lambda syntax, and is gated to void-returning event delegates so valid non-void delegate-returning lambdas are not rejected.
Changes
| File | Change |
|---|---|
CSharpExpressionHelpers.cs |
Adds interpolation-hole member-chain walking and lambda method-group detection for parenthesized and simple lambdas |
MemberResolver.cs |
Improves type-reference detection for static chains, C# aliases, containing namespaces, global usings, and using aliases |
SetPropertyHelpers.cs |
Validates interpolated member chains in TryHandleExpressionBinding() and reports MAUIX2017 from ConnectEvent() only for void-returning event delegates |
Descriptors.cs |
Adds the MAUIX2017 diagnostic descriptor |
AnalyzerReleases.Unshipped.md |
Registers MAUIX2017 |
ExpressionAnalyzer.cs |
Filters static type handlers during binding expression analysis |
CSharpExpressionDiagnosticsTests.cs |
Adds SourceGen diagnostic coverage for interpolated strings, static references, complex interpolation holes, missing members, lambda method groups, and discard-parameter suggestions |
Issues Fixed
N/A
Platforms Tested
- Android SourceGen diagnostic tests
</details>
---
<details>
<summary><strong>🏁 Report — Final Recommendation</strong></summary>
<br/>
## Comparative Fix Report — PR #35093
### Candidates Compared
| Rank | Candidate | Regression/Test Result | Assessment |
|---:|---|---|---|
| 1 | `pr-plus-reviewer` | ✅ SourceGen diagnostic suite passed in sandbox; XAML regression retry was environment-blocked before test execution | Best candidate. It keeps the PR's intended diagnostics and applies expert feedback for nested interpolation chains, alias/static type handling, simple lambdas, delegate return-type gating, and unrelated analyzer-release metadata. |
| 2 | `try-fix-1` | ✅ Passed SourceGen diagnostic suite and mandatory XAML regressions | Strong independent candidate. It fixes the major design gaps in the submitted PR through a Roslyn member-chain walker and delegate-aware lambda checks, but it did not include the additional alias-resolution and MAUIX2015/MAUIX2016 cleanup applied in `pr-plus-reviewer`. |
| 3 | `pr` | ✅ Gate passed | Solves the two narrow submitted regression tests, but expert review found unresolved correctness gaps: nested missing members are missed, `using` aliases can false-positive, syntax-only lambda detection can reject valid non-void delegate lambdas, simple lambdas are not covered, and unrelated MAUIX2015/MAUIX2016 release entries are present. |
### Notes on Failed or Blocked Tests
No candidate with a recorded regression-test failure was ranked above a passing candidate. `pr-plus-reviewer` did not fail the mandatory XAML regressions; the sandbox retry was blocked by environment prerequisites before tests executed. `try-fix-1` has the strongest recorded full regression evidence and therefore remains a close second, but `pr-plus-reviewer` is preferred because it is the PR fix with all expert-review feedback applied and still passes the focused SourceGen diagnostic suite.
### Winning Candidate
**Winner:** `pr-plus-reviewer`
`pr-plus-reviewer` is the best overall fix because it preserves the PR's source-generator diagnostic intent while addressing all expert-review findings in a sandbox patch. It also avoids broad semantic-compilation rewrites and remains localized to SourceGen helper/resolution paths plus analyzer metadata cleanup.
### Recommended Next Action
Update PR #35093 with the `pr-plus-reviewer` changes from the sandbox candidate, then run the mandatory XAML regressions in an environment with the required build tasks and targeting packs available.
</details>
</details>
<!-- SESSION:c937081 END -->
---
<details>
<summary><strong>🧭 Next Steps</strong> — review latest findings</summary>
<br/>
No alternative fix was selected for this run. Review the session findings and CI results before merging.
</details>
|
@StephaneDelcroix should it target net11? |
Resolve merge conflicts in the C# expressions source generator: - CSharpExpressionHelpers.cs: keep both the xmlns-prefix/attached-property helpers (this PR) and the interpolated-string identifier extraction + lambda method-group detection helpers (from inflight/current). - CSharpExpressionDiagnosticsTests.cs and CSharpExpressions.sgen.xaml.cs: keep the appended test methods from both branches. Also resolve a pre-existing MAUIX2017 diagnostic-ID collision on inflight/current (#35093 LambdaMethodGroupReference vs #35006 StructIntermediateNotSettable). LambdaMethodGroupReference keeps MAUIX2017; StructIntermediateNotSettable is renumbered to MAUIX2018 (descriptor, analyzer release notes, and its test updated accordingly). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2c02c03e-d835-437a-bb14-f9f2d0e108f3
…groups (#35093) <!-- 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! ## Description Two XAML C# expression (XEXPR) scenarios currently produce **C# compilation errors** instead of **source generator diagnostics**, making errors harder to understand and fix. This PR catches both at source generation time with clear messages. ### Issue 1: Interpolated string with nonexistent member ```xml <Label Text="{$'Hello, {Name1}!'}" /> ``` When `Name1` doesn't exist on the page or ViewModel, the source gen previously emitted `$"Hello, {__source.Name1}!"` verbatim, resulting in a cryptic C# compilation error. Now **MAUIX2009** is reported: _"'Name1' not found on 'MyPage' or 'MyViewModel'."_ **Root cause**: The existing MAUIX2009 check only ran for "simple identifiers" (`IsSimpleIdentifier` returns false for interpolated strings due to `$`, `{`, `}` chars). The fix extracts identifiers from interpolation holes using Roslyn and validates each against `MemberResolver`. ### Issue 2: Lambda with method group instead of invocation ```xml <Button Clicked="{(s, e) => this.OnCounterClicked}" /> ``` Missing `()` caused the source gen to emit `button.Clicked += (s, e) => this.OnCounterClicked;` — invalid C# (method group can't be expression body of void-returning lambda). Now **MAUIX2017** is reported: _"Lambda body 'this.OnCounterClicked' is a method group reference, not a method invocation. Did you mean 'this.OnCounterClicked(s, e)'?"_ **Root cause**: `ConnectEvent()` emitted lambda expressions with zero validation. The fix parses the lambda body with Roslyn to detect `MemberAccessExpression` without invocation. ## Changes | File | Change | |------|--------| | `CSharpExpressionHelpers.cs` | New `ExtractInterpolatedStringIdentifiers()` and `DetectLambdaMethodGroupReference()` helpers | | `SetPropertyHelpers.cs` | Interpolated string member validation in `TryHandleExpressionBinding()`; method group check in `ConnectEvent()` | | `Descriptors.cs` | New `MAUIX2017` diagnostic descriptor | | `AnalyzerReleases.Unshipped.md` | Register MAUIX2017 | | `CSharpExpressionDiagnosticsTests.cs` | 2 new tests (204/204 pass) | --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…groups (#35093) <!-- 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! ## Description Two XAML C# expression (XEXPR) scenarios currently produce **C# compilation errors** instead of **source generator diagnostics**, making errors harder to understand and fix. This PR catches both at source generation time with clear messages. ### Issue 1: Interpolated string with nonexistent member ```xml <Label Text="{$'Hello, {Name1}!'}" /> ``` When `Name1` doesn't exist on the page or ViewModel, the source gen previously emitted `$"Hello, {__source.Name1}!"` verbatim, resulting in a cryptic C# compilation error. Now **MAUIX2009** is reported: _"'Name1' not found on 'MyPage' or 'MyViewModel'."_ **Root cause**: The existing MAUIX2009 check only ran for "simple identifiers" (`IsSimpleIdentifier` returns false for interpolated strings due to `$`, `{`, `}` chars). The fix extracts identifiers from interpolation holes using Roslyn and validates each against `MemberResolver`. ### Issue 2: Lambda with method group instead of invocation ```xml <Button Clicked="{(s, e) => this.OnCounterClicked}" /> ``` Missing `()` caused the source gen to emit `button.Clicked += (s, e) => this.OnCounterClicked;` — invalid C# (method group can't be expression body of void-returning lambda). Now **MAUIX2017** is reported: _"Lambda body 'this.OnCounterClicked' is a method group reference, not a method invocation. Did you mean 'this.OnCounterClicked(s, e)'?"_ **Root cause**: `ConnectEvent()` emitted lambda expressions with zero validation. The fix parses the lambda body with Roslyn to detect `MemberAccessExpression` without invocation. ## Changes | File | Change | |------|--------| | `CSharpExpressionHelpers.cs` | New `ExtractInterpolatedStringIdentifiers()` and `DetectLambdaMethodGroupReference()` helpers | | `SetPropertyHelpers.cs` | Interpolated string member validation in `TryHandleExpressionBinding()`; method group check in `ConnectEvent()` | | `Descriptors.cs` | New `MAUIX2017` diagnostic descriptor | | `AnalyzerReleases.Unshipped.md` | Register MAUIX2017 | | `CSharpExpressionDiagnosticsTests.cs` | 2 new tests (204/204 pass) | --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…groups (#35093) <!-- 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! ## Description Two XAML C# expression (XEXPR) scenarios currently produce **C# compilation errors** instead of **source generator diagnostics**, making errors harder to understand and fix. This PR catches both at source generation time with clear messages. ### Issue 1: Interpolated string with nonexistent member ```xml <Label Text="{$'Hello, {Name1}!'}" /> ``` When `Name1` doesn't exist on the page or ViewModel, the source gen previously emitted `$"Hello, {__source.Name1}!"` verbatim, resulting in a cryptic C# compilation error. Now **MAUIX2009** is reported: _"'Name1' not found on 'MyPage' or 'MyViewModel'."_ **Root cause**: The existing MAUIX2009 check only ran for "simple identifiers" (`IsSimpleIdentifier` returns false for interpolated strings due to `$`, `{`, `}` chars). The fix extracts identifiers from interpolation holes using Roslyn and validates each against `MemberResolver`. ### Issue 2: Lambda with method group instead of invocation ```xml <Button Clicked="{(s, e) => this.OnCounterClicked}" /> ``` Missing `()` caused the source gen to emit `button.Clicked += (s, e) => this.OnCounterClicked;` — invalid C# (method group can't be expression body of void-returning lambda). Now **MAUIX2017** is reported: _"Lambda body 'this.OnCounterClicked' is a method group reference, not a method invocation. Did you mean 'this.OnCounterClicked(s, e)'?"_ **Root cause**: `ConnectEvent()` emitted lambda expressions with zero validation. The fix parses the lambda body with Roslyn to detect `MemberAccessExpression` without invocation. ## Changes | File | Change | |------|--------| | `CSharpExpressionHelpers.cs` | New `ExtractInterpolatedStringIdentifiers()` and `DetectLambdaMethodGroupReference()` helpers | | `SetPropertyHelpers.cs` | Interpolated string member validation in `TryHandleExpressionBinding()`; method group check in `ConnectEvent()` | | `Descriptors.cs` | New `MAUIX2017` diagnostic descriptor | | `AnalyzerReleases.Unshipped.md` | Register MAUIX2017 | | `CSharpExpressionDiagnosticsTests.cs` | 2 new tests (204/204 pass) | --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…groups (#35093) <!-- 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! ## Description Two XAML C# expression (XEXPR) scenarios currently produce **C# compilation errors** instead of **source generator diagnostics**, making errors harder to understand and fix. This PR catches both at source generation time with clear messages. ### Issue 1: Interpolated string with nonexistent member ```xml <Label Text="{$'Hello, {Name1}!'}" /> ``` When `Name1` doesn't exist on the page or ViewModel, the source gen previously emitted `$"Hello, {__source.Name1}!"` verbatim, resulting in a cryptic C# compilation error. Now **MAUIX2009** is reported: _"'Name1' not found on 'MyPage' or 'MyViewModel'."_ **Root cause**: The existing MAUIX2009 check only ran for "simple identifiers" (`IsSimpleIdentifier` returns false for interpolated strings due to `$`, `{`, `}` chars). The fix extracts identifiers from interpolation holes using Roslyn and validates each against `MemberResolver`. ### Issue 2: Lambda with method group instead of invocation ```xml <Button Clicked="{(s, e) => this.OnCounterClicked}" /> ``` Missing `()` caused the source gen to emit `button.Clicked += (s, e) => this.OnCounterClicked;` — invalid C# (method group can't be expression body of void-returning lambda). Now **MAUIX2017** is reported: _"Lambda body 'this.OnCounterClicked' is a method group reference, not a method invocation. Did you mean 'this.OnCounterClicked(s, e)'?"_ **Root cause**: `ConnectEvent()` emitted lambda expressions with zero validation. The fix parses the lambda body with Roslyn to detect `MemberAccessExpression` without invocation. ## Changes | File | Change | |------|--------| | `CSharpExpressionHelpers.cs` | New `ExtractInterpolatedStringIdentifiers()` and `DetectLambdaMethodGroupReference()` helpers | | `SetPropertyHelpers.cs` | Interpolated string member validation in `TryHandleExpressionBinding()`; method group check in `ConnectEvent()` | | `Descriptors.cs` | New `MAUIX2017` diagnostic descriptor | | `AnalyzerReleases.Unshipped.md` | Register MAUIX2017 | | `CSharpExpressionDiagnosticsTests.cs` | 2 new tests (204/204 pass) | --------- Co-authored-by: Copilot <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!
Description
Two XAML C# expression (XEXPR) scenarios currently produce C# compilation errors instead of source generator diagnostics, making errors harder to understand and fix. This PR catches both at source generation time with clear messages.
Issue 1: Interpolated string with nonexistent member
When
Name1doesn't exist on the page or ViewModel, the source gen previously emitted$"Hello, {__source.Name1}!"verbatim, resulting in a cryptic C# compilation error. Now MAUIX2009 is reported: "'Name1' not found on 'MyPage' or 'MyViewModel'."Root cause: The existing MAUIX2009 check only ran for "simple identifiers" (
IsSimpleIdentifierreturns false for interpolated strings due to$,{,}chars). The fix extracts identifiers from interpolation holes using Roslyn and validates each againstMemberResolver.Issue 2: Lambda with method group instead of invocation
Missing
()caused the source gen to emitbutton.Clicked += (s, e) => this.OnCounterClicked;— invalid C# (method group can't be expression body of void-returning lambda). Now MAUIX2017 is reported: "Lambda body 'this.OnCounterClicked' is a method group reference, not a method invocation. Did you mean 'this.OnCounterClicked(s, e)'?"Root cause:
ConnectEvent()emitted lambda expressions with zero validation. The fix parses the lambda body with Roslyn to detectMemberAccessExpressionwithout invocation.Changes
CSharpExpressionHelpers.csExtractInterpolatedStringIdentifiers()andDetectLambdaMethodGroupReference()helpersSetPropertyHelpers.csTryHandleExpressionBinding(); method group check inConnectEvent()Descriptors.csMAUIX2017diagnostic descriptorAnalyzerReleases.Unshipped.mdCSharpExpressionDiagnosticsTests.cs