Skip to content

Improve XEXPR diagnostics for interpolated strings and lambda method groups - #35093

Merged
kubaflo merged 2 commits into
inflight/currentfrom
fix/xexpr-better-diagnostics
Jul 22, 2026
Merged

kubaflo merged 2 commits into
inflight/currentfrom
fix/xexpr-better-diagnostics

Conversation

@StephaneDelcroix

Copy link
Copy Markdown
Contributor

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

<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

<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)

Copilot AI review requested due to automatic review settings April 22, 2026 14:41
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

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

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

Or

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR 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.

Comment on lines +718 to +732
// 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)
}
}
}

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +719 to +721
if (expression.Code.StartsWith("$\"", StringComparison.Ordinal) || expression.Code.StartsWith("$@\"", StringComparison.Ordinal))
{
var interpolatedIds = CSharpExpressionHelpers.ExtractInterpolatedStringIdentifiers(expression.Code);

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Suggested change
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);

Copilot uses AI. Check for mistakes.
Comment on lines +969 to +981
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;
}

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Suggested change
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);
}

Copilot uses AI. Check for mistakes.
Assert.Contains(result.Diagnostics, d => d.Id == "MAUIX2009");
}

[Fact]

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Suggested change
[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]

Copilot uses AI. Check for mistakes.
@StephaneDelcroix

Copy link
Copy Markdown
Contributor Author

@noiseonwires

@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Apr 24, 2026
@dotnet dotnet deleted a comment from MauiBot Apr 24, 2026
@MauiBot MauiBot added the s/agent-fix-win AI found a better alternative fix than the PR label Apr 24, 2026

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@kubaflo

kubaflo commented May 24, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/refactor-copilot-yml

@MauiBot

This comment has been minimized.

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you please resolve conflicts?

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 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();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 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(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 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.

@MauiBot MauiBot added s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-win AI found a better alternative fix than the PR labels Jul 7, 2026
MauiBot

This comment was marked as outdated.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jul 7, 2026
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>
Copilot AI review requested due to automatic review settings July 7, 2026 12:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

}

// Validate identifiers inside interpolated string holes
if (expression.Code.StartsWith("$\"", StringComparison.Ordinal) || expression.Code.StartsWith("$@\"", StringComparison.Ordinal))
Comment on lines +1088 to +1112
// 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));
}
@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 7, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 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)))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 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);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 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();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 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 MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Gate Passed Confidence Low Platform Android


🗂️ 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.cs
  • src/Controls/src/SourceGen/Descriptors.cs
  • src/Controls/src/SourceGen/ExpressionAnalyzer.cs
  • src/Controls/src/SourceGen/MemberResolver.cs
  • src/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

  • gh is unauthenticated, so PR metadata/comments were gathered from the public GitHub API and local origin/main...HEAD diff.
  • 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 / MAUIX2016 descriptor/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 as DateTime.Now.Day and aliases such as string.Empty can still false-positive as MAUIX2009.
  • ✗ CSharpExpressionHelpers.cs / SetPropertyHelpers.cs: nested missing members such as User.MissingName are not validated.
  • ✗ CSharpExpressionHelpers.cs / SetPropertyHelpers.cs: method-group lambda detection is syntax-only and can reject valid non-void delegate lambdas.
  • ⚠ CSharpExpressionHelpers.cs: SimpleLambdaExpressionSyntax is not checked.
  • ⚠ Descriptors.cs / AnalyzerReleases.Unshipped.md: MAUIX2015 and MAUIX2016 appear 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: gh is 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 false MAUIX2009.
  • Valid alias type ({$'{string.Empty}'}): risks false MAUIX2009.
  • Nested missing property ({$'{User.Missing}'}): likely falls through instead of reporting MAUIX2009.
  • 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}): misses MAUIX2017.

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>

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jul 7, 2026
@kubaflo

kubaflo commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@StephaneDelcroix should it target net11?

@kubaflo
kubaflo changed the base branch from main to inflight/current July 22, 2026 23:28
@kubaflo
kubaflo merged commit c275eac into inflight/current Jul 22, 2026
29 of 37 checks passed
@kubaflo
kubaflo deleted the fix/xexpr-better-diagnostics branch July 22, 2026 23:29
@github-actions github-actions Bot added this to the .NET 10 SR10 milestone Jul 22, 2026
StephaneDelcroix added a commit that referenced this pull request Jul 27, 2026
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
kubaflo pushed a commit that referenced this pull request Jul 28, 2026
…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>
kubaflo pushed a commit that referenced this pull request Jul 29, 2026
…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>
kubaflo pushed a commit that referenced this pull request Aug 7, 2026
…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>
kubaflo pushed a commit that referenced this pull request Aug 12, 2026
…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>
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 22, 2026
@sheiksyedm sheiksyedm modified the milestones: .NET 10 SR10, .NET 10 SR11 Aug 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants