Skip to content

Fix compiled binding generation for null-conditional SetBinding calls - #38651

Merged
kubaflo merged 3 commits into
dotnet:inflight/currentfrom
Shalini-Ashokan:fix/issue-38365-null-conditional-binding
Sep 21, 2026
Merged

kubaflo merged 3 commits into
dotnet:inflight/currentfrom
Shalini-Ashokan:fix/issue-38365-null-conditional-binding

Conversation

@Shalini-Ashokan

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!

Issue Details

label?.SetBinding(...) should create a compiled binding when label is not null.
Instead, MAUI did not generate the required interceptor, so the app compiled but threw Call to SetBinding<...> was not intercepted at runtime.

Root Cause

label.SetBinding(...) and label?.SetBinding(...) have different C# syntax-tree shapes.
The MAUI generator recognized only the ordinary call shape, so it skipped null-conditional calls and did not generate an interceptor.

Description of Change

GetInvokedMethodName now recognizes both syntax types.
Therefore, ordinary and null-conditional SetBinding calls follow the same interceptor-generation process.

Validated the behavior in the following platforms

  • Android
  • Windows
  • iOS
  • Mac

Issues Fixed

Fixes #38365

Output ScreenShot

Before After
38365-BeforeFix.mov
38365-AfterFix.mov

Shalini-Ashokan and others added 3 commits September 10, 2026 14:57
@Shalini-Ashokan
Shalini-Ashokan deployed to copilot-pat-pool September 18, 2026 07:30 — with GitHub Actions Active
@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 -- 38651

Or

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

@dotnet-policy-service dotnet-policy-service Bot added the community ✨ Community Contribution label Sep 18, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Hey there @@Shalini-Ashokan! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Sep 18, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

@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 Sep 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Warning

Your /review couldn't start: the pipeline branch improved-reviewer doesn't exist. Use --branch improved-reviewer (the current reviewer branch), or omit --branch to run on main. Valid platforms: android, ios, catalyst, windows (e.g. /review android --branch improved-reviewer).

@github-actions github-actions Bot removed the s/agent-review-in-progress AI review is currently running for this PR label Sep 18, 2026
@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 Sep 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Note

🔍 /review started

AzDO build 15379548 is running for android.

The s/agent-review-in-progress label stays on this PR while the run is active. The final recommendation and outcome labels are posted only after Gate, expert review, and Deep UI tests finish.

MauiBot

This comment was marked as outdated.

@MauiBot MauiBot added s/agent-approved AI agent recommends approval - PR fix is correct and optimal 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 s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) and removed s/agent-review-in-progress AI review is currently running for this PR labels Sep 18, 2026
@sheiksyedm

Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests , maui-pr-devicetests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

@kubaflo

This comment has been minimized.

@github-actions

This comment has been minimized.

@Shalini-Ashokan Shalini-Ashokan changed the title [WIP] Fix compiled binding generation for null-conditional SetBinding calls Fix compiled binding generation for null-conditional SetBinding calls Sep 21, 2026
@sheiksyedm
sheiksyedm marked this pull request as ready for review September 21, 2026 12:50
@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 Sep 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Note

🔍 /review started

AzDO build 15397264 is running for android.

The s/agent-review-in-progress label stays on this PR while the run is active. The final recommendation and outcome labels are posted only after Gate, expert review, and Deep UI tests finish.

@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

@Shalini-Ashokan — new AI review results are available based on commit 6b4b1f9.

Gate Passed Confidence Unknown Platform Android


🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix

Gate Result: ✅ PASSED

Platform: ANDROID · Base: main · Merge base: f8a2f062

Test Without Fix (expect FAIL) With Fix (expect PASS)
🧪 IntegrationTests IntegrationTests ✅ FAIL — 139s ✅ PASS — 124s
🔴 Without fix — 🧪 IntegrationTests: FAIL ✅ · 139s

Error-relevant lines (filtered from the build log):

     at BindingSourceGen.UnitTests.IntegrationTests.GenerateBindingForNullConditionalTarget() in /_/src/Controls/tests/BindingSourceGen.UnitTests/IntegrationTests.cs:line 168
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
🟢 With fix — 🧪 IntegrationTests: PASS ✅ · 124s

(no coded error found; showing last 1200 chars)

egrationTests.GenerateBindingWithForcedConditionalAccessAfterCast [252 ms]
  Passed BindingSourceGen.UnitTests.IntegrationTests.IgnoresOtherOtherCreateMethod [141 ms]
  Passed BindingSourceGen.UnitTests.IntegrationTests.GenerateBindingForNullConditionalTarget [179 ms]
  Passed BindingSourceGen.UnitTests.IntegrationTests.IgnoresOtherBindingCreateMethod [452 ms]
  Passed BindingSourceGen.UnitTests.IntegrationTests.GenerateSimpleBinding_WithInitSetter [346 ms]
  Passed BindingSourceGen.UnitTests.IntegrationTests.GeneratedMethodNameUsesStableLocationId [295 ms]
  Passed BindingSourceGen.UnitTests.IntegrationTests.GenerateSimpleBindingWhenNullableDisabledNonNullableValueType [191 ms]
  Passed BindingSourceGen.UnitTests.IntegrationTests.GenerateBindingWithPrivateReadonlyProperty [198 ms]
[xUnit.net 00:00:13.49]   Finished:    Controls.BindingSourceGen.UnitTests
  Passed BindingSourceGen.UnitTests.IntegrationTests.GenerateBindingWithNullableObjectAndNullableReferenceTypeProperty_SetBinding [218 ms]
  Passed BindingSourceGen.UnitTests.IntegrationTests.GenerateBindingWithNonNullableReferenceTypesCasts [174 ms]
Test Run Successful.
Total tests: 33
     Passed: 33
 Total time: 14.7863 Seconds
📁 Fix files reverted (2 files)
  • src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs
  • src/Controls/src/BindingSourceGen/InvocationParser.cs

📋 Pre-Flight — Context & Validation

PR #38651 Pre-Flight

Scope

  • PR: Fix compiled binding generation for null-conditional SetBinding calls
  • Issue: #38365, Compiled-binding interceptor not generated for SetBinding call wrapped in null-conditional access (?.)
  • Base / platform: main / Android
  • Review branch / commit: pr-review-38651 / 962069a8f5e77adb83c623d67430b70c1bc15979
  • Gate: Passed previously. The IntegrationTests class fails without the PR fix and passes with it; this phase must not repeat gate verification.

Problem

An eligible compiled-binding call such as label?.SetBinding(...) is represented by Roslyn as an invocation whose expression is a MemberBindingExpressionSyntax, rather than the MemberAccessExpressionSyntax used by label.SetBinding(...). The generator's syntax predicate therefore skips the null-conditional call, emits no interceptor, and the application later throws InvalidOperationException when the generic SetBinding call executes.

Current PR Approach

The PR changes 3 files (+36/-5):

  • BindingSourceGenerator.cs adds GetInvokedMethodName(ExpressionSyntax), mapping both MemberAccessExpressionSyntax and MemberBindingExpressionSyntax to their SimpleNameSyntax. The helper is used by the syntax predicate and source-location extraction.
  • InvocationParser.cs uses that same helper instead of directly casting the invocation expression to MemberAccessExpressionSyntax.
  • IntegrationTests.cs adds GenerateBindingForNullConditionalTarget, asserting clean diagnostics, a generated binding, and an interceptable location.

This approach is a shared syntax-shape abstraction. Alternatives must not merely rename or relocate the same helper; they need a different mechanism-level strategy.

Allowed Production Files

EstablishBrokenBaseline.ps1 previously detected these two revertable fix files:

  • src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs
  • src/Controls/src/BindingSourceGen/InvocationParser.cs

The test file is retained at the PR version and is read-only during try-fix attempts. Each attempt must honor .github/.baseline-state.json after establishing its baseline.

Bounded Validation

Run only the primary regression and its mandatory neighboring regression slice:

dotnet test src/Controls/tests/BindingSourceGen.UnitTests/Controls.BindingSourceGen.UnitTests.csproj --filter "FullyQualifiedName~BindingSourceGen.UnitTests.IntegrationTests"

This executes the 33 IntegrationTests, including GenerateBindingForNullConditionalTarget; it is not the full project suite. Each candidate is limited to one implementation/test pass and at most one focused correction/retest.

Constraints

  • At most two candidates, sequentially: gpt-5.3-codex, then gpt-5.6-sol.
  • Each child invokes the try-fix skill exactly once and performs its inline expert self-review; no additional reviewer agents.
  • Preserve all attempt artifacts and always restore through pwsh .github/scripts/EstablishBrokenBaseline.ps1 -Restore.
  • Do not modify or overwrite PRAgent/gate/content.md.

🔬 Code Review — Deep Analysis

Expert Evaluation — PR #38651

Verdict: NEEDS_DISCUSSION
Confidence: low

Independent Assessment

The submitted change correctly recognizes both ordinary member access (label.SetBinding(...)) and conditional member binding (label?.SetBinding(...)) through a shared GetInvokedMethodName(ExpressionSyntax) helper. Reusing that helper in syntax filtering, source-location extraction, and invocation parsing keeps the Roslyn syntax-shape handling consistent and avoids the unsafe direct casts that previously excluded the null-conditional form.

The approach is narrow and sound:

  • Null-conditional SetBinding calls now reach semantic verification and interceptor generation.
  • Existing direct SetBinding behavior is preserved.
  • Binding.Create candidate filtering remains unchanged, while parser dispatch continues to recognize it.
  • Unsupported expression shapes now produce the existing UnableToResolvePath diagnostic path rather than failing through an invalid cast.
  • The new regression test covers clean diagnostics, generated binding presence, and an interceptable location.

Findings

No actionable code-level defects were found. The required inline findings artifact contains an empty JSON array.

The reviewer identified one non-blocking test-strengthening opportunity: assert a stable property of the generated interceptor output rather than only its presence. This can improve regression precision if it can be expressed using existing test conventions without coupling the test to incidental generated formatting.

CI and Gate Evidence

The expert reviewer could not independently observe required GitHub checks and therefore conservatively returned NEEDS_DISCUSSION with low confidence. The trusted workflow Gate supplied to this comparison is stronger task-specific evidence: the focused regression fails without the submitted fix and passes with it. Per the execution contract, that Gate is accepted and is not rerun.

Refinement Recommendation

Consider one consolidated pr-plus-reviewer refinement limited to strengthening the new regression test. Do not alter the production fix unless the focused inspection identifies a concrete issue.


🛠️ Try-Fix — Analysis & Comparison

STEP 5a Try-Fix Aggregate

Candidate 1 — Inline Syntax-Shape Matching

Model: gpt-5.3-codex

Approach: Recognize MemberAccessExpressionSyntax and MemberBindingExpressionSyntax through local switch patterns at each affected consumption site: the IsSetBindingMethod predicate, source-location extraction in GetBindingForGeneration, and dispatch in InvocationParser.ParseInvocation.

Difference from the PR: The PR centralizes method-name extraction in a shared GetInvokedMethodName(ExpressionSyntax) helper. Candidate 1 deliberately avoids that abstraction and performs local shape-specific matching independently at each site. It fixes the same AST mismatch through duplicated local patterns rather than shared extraction.

Files changed:

  • src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs
  • src/Controls/src/BindingSourceGen/InvocationParser.cs

Captured diff:

diff --git a/src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs b/src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs
index 083df614a4..99832565ee 100644
--- a/src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs
+++ b/src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs
@@ -92,8 +92,12 @@ public class BindingSourceGenerator : IIncrementalGenerator
 	private static bool IsSetBindingMethod(SyntaxNode node)
 	{
 		return node is InvocationExpressionSyntax invocation
-			&& invocation.Expression is MemberAccessExpressionSyntax method
-			&& method.Name.Identifier.Text == "SetBinding"
+			&& invocation.Expression switch
+			{
+				MemberAccessExpressionSyntax memberAccess => memberAccess.Name.Identifier.Text == "SetBinding",
+				MemberBindingExpressionSyntax memberBinding => memberBinding.Name.Identifier.Text == "SetBinding",
+				_ => false
+			}
 			&& invocation.ArgumentList.Arguments.Count >= 2
 			&& invocation.ArgumentList.Arguments[1].Expression is not LiteralExpressionSyntax
 			&& invocation.ArgumentList.Arguments[1].Expression is not ObjectCreationExpressionSyntax;
@@ -137,7 +141,16 @@ public class BindingSourceGenerator : IIncrementalGenerator
 		var enabledNullable = IsNullableContextEnabled(context);
 
 		var invocation = (InvocationExpressionSyntax)context.Node;
-		var method = (MemberAccessExpressionSyntax)invocation.Expression;
+		var methodName = invocation.Expression switch
+		{
+			MemberAccessExpressionSyntax memberAccess => memberAccess.Name,
+			MemberBindingExpressionSyntax memberBinding => memberBinding.Name,
+			_ => null
+		};
+		if (methodName is null)
+		{
+			return Result<BindingInvocationDescription>.Failure(DiagnosticsFactory.UnableToResolvePath(invocation.GetLocation()));
+		}
 
 		var invocationParser = new InvocationParser(context);
 		var interceptedMethodTypeResult = invocationParser.ParseInvocation(invocation, t);
@@ -151,7 +164,7 @@ public class BindingSourceGenerator : IIncrementalGenerator
 		var interceptableLocation = context.SemanticModel.GetInterceptableLocation(invocation, t);
 #pragma warning restore RSEXPERIMENTAL002
 
-		var sourceCodeLocation = SourceCodeLocation.CreateFrom(method.Name.GetLocation());
+		var sourceCodeLocation = SourceCodeLocation.CreateFrom(methodName.GetLocation());
diff --git a/src/Controls/src/BindingSourceGen/InvocationParser.cs b/src/Controls/src/BindingSourceGen/InvocationParser.cs
index 006103c280..c26dcfd1d5 100644
--- a/src/Controls/src/BindingSourceGen/InvocationParser.cs
+++ b/src/Controls/src/BindingSourceGen/InvocationParser.cs
@@ -20,10 +20,14 @@ internal class InvocationParser
 
 	internal Result<InterceptedMethodType> ParseInvocation(InvocationExpressionSyntax invocationSyntax, CancellationToken t)
 	{
-		return ((MemberAccessExpressionSyntax)invocationSyntax.Expression).Name.Identifier.Text switch
+		return invocationSyntax.Expression switch
 		{
-			InterceptedMethodsNames.SetBinding => VerifyCorrectOverloadSetBinding(invocationSyntax, t),
-			InterceptedMethodsNames.Create => VerifyCorrectOverloadBindingCreate(invocationSyntax, t),
+			MemberAccessExpressionSyntax { Name.Identifier.Text: InterceptedMethodsNames.SetBinding }
+				or MemberBindingExpressionSyntax { Name.Identifier.Text: InterceptedMethodsNames.SetBinding }
+				=> VerifyCorrectOverloadSetBinding(invocationSyntax, t),
+			MemberAccessExpressionSyntax { Name.Identifier.Text: InterceptedMethodsNames.Create }
+				or MemberBindingExpressionSyntax { Name.Identifier.Text: InterceptedMethodsNames.Create }
+				=> VerifyCorrectOverloadBindingCreate(invocationSyntax, t),
 			_ => throw new NotSupportedException()
 		};
 	}

Test command:

dotnet test src/Controls/tests/BindingSourceGen.UnitTests/Controls.BindingSourceGen.UnitTests.csproj --filter "FullyQualifiedName~BindingSourceGen.UnitTests.IntegrationTests"

Test result: Pass on the first and only execution: 33 passed, 0 failed, 0 skipped.

Inline self-review: Clean, 0 findings (reviewer-findings.json contains []).

Analysis: Local handling of both Roslyn invocation shapes admits the null-conditional call into generation, obtains the method-name location, and dispatches it as SetBinding, so the retained regression receives an interceptor. The candidate is functional but repeats the same shape mapping three times, whereas the PR keeps that knowledge in one helper.

Artifacts: CustomAgentLogsTmp/PRState/38651/PRAgent/try-fix/attempt-1/

Restoration: EstablishBrokenBaseline.ps1 -Restore completed with Restored True; .github/.baseline-state.json was cleared.

Candidate 2 — Select the Method Name as the Syntax Candidate

Model: gpt-5.6-sol

Approach: Change the incremental syntax provider's selection unit from the enclosing InvocationExpressionSyntax to the invoked SimpleNameSyntax (SetBinding or Create). Both direct and null-conditional calls expose the same method-name child node. The transform walks from that selected name to its invocation, passes the selected node to InvocationParser, and uses it directly for source location. A terminal-span check prevents a receiver named SetBinding from being mistaken for the invoked method.

Difference from the PR and Candidate 1: The PR extracts a name from either of two enclosing expression shapes through a shared helper. Candidate 1 performs the same two-shape mapping locally at three sites. Candidate 2 does not map enclosing shapes at all: it selects the common child name directly, so predicate selection, dispatch, and location all begin with the stable node needed by the generator. Its mechanism is selection rather than extraction.

Files changed:

  • src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs
  • src/Controls/src/BindingSourceGen/InvocationParser.cs

Captured diff:

diff --git a/src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs b/src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs
index 083df614a4..b1ab510716 100644
--- a/src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs
+++ b/src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs
@@ -91,9 +91,9 @@ public class BindingSourceGenerator : IIncrementalGenerator
 
 	private static bool IsSetBindingMethod(SyntaxNode node)
 	{
-		return node is InvocationExpressionSyntax invocation
-			&& invocation.Expression is MemberAccessExpressionSyntax method
-			&& method.Name.Identifier.Text == "SetBinding"
+		return node is SimpleNameSyntax { Identifier.Text: "SetBinding" } method
+			&& method.Parent?.Parent is InvocationExpressionSyntax invocation
+			&& method.Span.End == invocation.Expression.Span.End
 			&& invocation.ArgumentList.Arguments.Count >= 2
 			&& invocation.ArgumentList.Arguments[1].Expression is not LiteralExpressionSyntax
 			&& invocation.ArgumentList.Arguments[1].Expression is not ObjectCreationExpressionSyntax;
@@ -101,13 +101,13 @@ public class BindingSourceGenerator : IIncrementalGenerator
 
 	private static bool IsCreateMethod(SyntaxNode node)
 	{
-		return node is InvocationExpressionSyntax invocation
-			&& invocation.Expression is MemberAccessExpressionSyntax method
-			&& method.Name.Identifier.Text == "Create"
+		return node is SimpleNameSyntax { Identifier.Text: "Create" } method
+			&& method.Parent is MemberAccessExpressionSyntax memberAccess
+			&& memberAccess.Parent is InvocationExpressionSyntax invocation
 			&& invocation.ArgumentList.Arguments.Count >= 1
 			&& invocation.ArgumentList.Arguments[0].Expression is not LiteralExpressionSyntax
 			&& invocation.ArgumentList.Arguments[0].Expression is not ObjectCreationExpressionSyntax
-			&& GetTypeNameFromExpression(method.Expression) switch
+			&& GetTypeNameFromExpression(memberAccess.Expression) switch
 			{
 				"Binding" => true,
 				"BindingBase" => true,
@@ -136,11 +136,11 @@ public class BindingSourceGenerator : IIncrementalGenerator
 	{
 		var enabledNullable = IsNullableContextEnabled(context);
 
-		var invocation = (InvocationExpressionSyntax)context.Node;
-		var method = (MemberAccessExpressionSyntax)invocation.Expression;
+		var method = (SimpleNameSyntax)context.Node;
+		var invocation = (InvocationExpressionSyntax)method.Parent!.Parent!;
 
 		var invocationParser = new InvocationParser(context);
-		var interceptedMethodTypeResult = invocationParser.ParseInvocation(invocation, t);
+		var interceptedMethodTypeResult = invocationParser.ParseInvocation(invocation, method, t);
 
 		if (interceptedMethodTypeResult.HasDiagnostics)
 		{
@@ -151,7 +151,7 @@ public class BindingSourceGenerator : IIncrementalGenerator
 		var interceptableLocation = context.SemanticModel.GetInterceptableLocation(invocation, t);
 #pragma warning restore RSEXPERIMENTAL002
 
-		var sourceCodeLocation = SourceCodeLocation.CreateFrom(method.Name.GetLocation());
+		var sourceCodeLocation = SourceCodeLocation.CreateFrom(method.GetLocation());
diff --git a/src/Controls/src/BindingSourceGen/InvocationParser.cs b/src/Controls/src/BindingSourceGen/InvocationParser.cs
index 006103c280..8ab4542b26 100644
--- a/src/Controls/src/BindingSourceGen/InvocationParser.cs
+++ b/src/Controls/src/BindingSourceGen/InvocationParser.cs
@@ -18,9 +18,9 @@ internal class InvocationParser
 		internal const string Create = nameof(Create);
 	}
 
-	internal Result<InterceptedMethodType> ParseInvocation(InvocationExpressionSyntax invocationSyntax, CancellationToken t)
+	internal Result<InterceptedMethodType> ParseInvocation(InvocationExpressionSyntax invocationSyntax, SimpleNameSyntax method, CancellationToken t)
 	{
-		return ((MemberAccessExpressionSyntax)invocationSyntax.Expression).Name.Identifier.Text switch
+		return method.Identifier.Text switch
 		{
 			InterceptedMethodsNames.SetBinding => VerifyCorrectOverloadSetBinding(invocationSyntax, t),
 			InterceptedMethodsNames.Create => VerifyCorrectOverloadBindingCreate(invocationSyntax, t),

Test command:

dotnet test src/Controls/tests/BindingSourceGen.UnitTests/Controls.BindingSourceGen.UnitTests.csproj --filter "FullyQualifiedName~BindingSourceGen.UnitTests.IntegrationTests"

Test result: Pass on the first and only execution: 33 passed, 0 failed, 0 skipped.

Inline self-review: Clean, 0 findings (reviewer-findings.json contains []); the reviewed diff remained unchanged after testing.

Analysis: Selecting the common method-name node makes the provider independent of whether Roslyn encloses it in ordinary member access or conditional member binding. The transform can therefore reach semantic verification and interceptor generation without expression-shape normalization. This passed the bounded regression slice, though it changes the provider's candidate granularity and relies on syntax-parent relationships plus the terminal-span guard.

Artifacts: CustomAgentLogsTmp/PRState/38651/PRAgent/try-fix/attempt-2/

Restoration: EstablishBrokenBaseline.ps1 -Restore completed with Restored True; .github/.baseline-state.json was cleared and both target files had an empty working-tree diff.


🏁 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Winner: pr-plus-reviewer

The submitted production fix is correct and uses the strongest implementation design: one shared syntax-shape abstraction handles both ordinary member access and conditional member binding consistently across candidate filtering, location extraction, and parser dispatch. The expert reviewer found no actionable code defect and produced no inline findings. pr-plus-reviewer wins narrowly because it retains that implementation unchanged while strengthening the new regression test to prove that the null-conditional input emits the expected stable-ID SetBinding interceptor method.

Candidate Comparison

Rank Candidate Mechanism Regression evidence Assessment
1 pr-plus-reviewer Submitted shared GetInvokedMethodName abstraction plus a generated-interceptor assertion Focused IntegrationTests: 33 passed, 0 failed, 0 skipped Best combination of centralized production logic and the most precise regression coverage
2 pr Shared helper maps MemberAccessExpressionSyntax and MemberBindingExpressionSyntax to SimpleNameSyntax Trusted Gate: regression fails without the fix and passes with it Production fix is sound and minimal; test proves generation state and location, but not the emitted interceptor shape directly
3 try-fix-1 Repeats local syntax-shape switches at the predicate, transform, and parser Focused IntegrationTests: 33 passed, 0 failed, 0 skipped Functional and localized, but duplicates the same Roslyn shape knowledge at three sites and is more vulnerable to future drift
4 try-fix-2 Changes the incremental provider to select SimpleNameSyntax and walks parent relationships to the invocation Focused IntegrationTests: 33 passed, 0 failed, 0 skipped Functional and conceptually distinct, but changes candidate granularity for both SetBinding and Create and relies on parent/span invariants, creating more regression surface than needed

All four candidates passed their applicable regression evidence, so no candidate required the mandatory failed-test demotion. The ranking is therefore based on correctness clarity, maintenance cost, blast radius, and test precision.

Expert Review Reconciliation

  • Inline findings: none (inline-findings.json is []).
  • Production behavior: the expert reviewer confirmed that null-conditional SetBinding calls now enter semantic verification and interceptor generation, direct SetBinding remains unchanged, and Binding.Create filtering is not broadened.
  • Reviewer refinement: only the generated-output assertion was actionable as an improvement; no production change was warranted.
  • CI uncertainty: the expert pass could not independently observe GitHub required checks. The task's trusted Gate explicitly passed and demonstrated fail-without-fix/pass-with-fix behavior, so no additional Gate run was performed.

Required Change

Apply the two-line test refinement captured in pr-plus-reviewer/reviewer.patch. No changes to the submitted production implementation are recommended.


📱 UI Tests — Button,Label,Layout

Detected UI test categories: Button,Label,Layout

✅ Deep UI tests — 422 passed, 0 failed, 7 skipped across 3 categories on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
Button 104/106 (2 skipped) ✓ —
Label 123/125 (2 skipped) ✓ —
Layout 195/198 (3 skipped) ✓ —
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

🧭 Next Steps — reviewer changes required

The reviewer-enhanced candidate identified changes that are not yet in the submitted PR.

Why: The submitted shared-helper implementation is the clearest and lowest-risk production fix. pr-plus-reviewer preserves it unchanged and adds a focused assertion proving that the null-conditional case emits the expected stable-ID SetBinding interceptor, with all 33 targeted tests passing.

Address the actionable findings in this review before merging.

@MauiBot MauiBot added 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 and removed s/agent-approved AI agent recommends approval - PR fix is correct and optimal s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-review-in-progress AI review is currently running for this PR labels Sep 21, 2026
@kubaflo

This comment has been minimized.

@kubaflo
kubaflo changed the base branch from main to inflight/current September 21, 2026 16:06
@kubaflo
kubaflo merged commit 1091c9d into dotnet:inflight/current Sep 21, 2026
196 of 205 checks passed
@github-actions github-actions Bot added this to the .NET 10 SR12 milestone Sep 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Tests Failure Analysis

@Shalini-Ashokan — test-failure analysis for commit 6b4b1f9.

Scope CI failures Commit 6b4b1f9


🧪 CI Analysis — click to expand
📊 maui-pr

No failures found.


🧪 maui-pr-devicetests

🧪 maui-pr-uitests

🧭 Follow-up — actions and refresh

Next action: Rerun the unresolved device legs and iOS Gestures shard, retaining the underlying build/crash diagnostics.

Maintainers: comment /review tests to refresh this report.

kubaflo pushed a commit that referenced this pull request Sep 21, 2026
…#38651)

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

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
label?.SetBinding(...) should create a compiled binding when label is
not null.
Instead, MAUI did not generate the required interceptor, so the app
compiled but threw Call to SetBinding<...> was not intercepted at
runtime.

### Root Cause
label.SetBinding(...) and label?.SetBinding(...) have different C#
syntax-tree shapes.
The MAUI generator recognized only the ordinary call shape, so it
skipped null-conditional calls and did not generate an interceptor.

### Description of Change
GetInvokedMethodName now recognizes both syntax types.
Therefore, ordinary and null-conditional SetBinding calls follow the
same interceptor-generation process.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes #38365 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/0c5a735d-4a06-4261-8488-f19f464a2be9"
>| <video
src="https://github.com/user-attachments/assets/b700be4b-776f-4def-bbd2-8325d1007bd1">|
kubaflo pushed a commit that referenced this pull request Sep 27, 2026
…#38651)

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

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
label?.SetBinding(...) should create a compiled binding when label is
not null.
Instead, MAUI did not generate the required interceptor, so the app
compiled but threw Call to SetBinding<...> was not intercepted at
runtime.

### Root Cause
label.SetBinding(...) and label?.SetBinding(...) have different C#
syntax-tree shapes.
The MAUI generator recognized only the ordinary call shape, so it
skipped null-conditional calls and did not generate an interceptor.

### Description of Change
GetInvokedMethodName now recognizes both syntax types.
Therefore, ordinary and null-conditional SetBinding calls follow the
same interceptor-generation process.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes #38365 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/0c5a735d-4a06-4261-8488-f19f464a2be9"
>| <video
src="https://github.com/user-attachments/assets/b700be4b-776f-4def-bbd2-8325d1007bd1">|
kubaflo pushed a commit that referenced this pull request Sep 27, 2026
…#38651)

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

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
label?.SetBinding(...) should create a compiled binding when label is
not null.
Instead, MAUI did not generate the required interceptor, so the app
compiled but threw Call to SetBinding<...> was not intercepted at
runtime.

### Root Cause
label.SetBinding(...) and label?.SetBinding(...) have different C#
syntax-tree shapes.
The MAUI generator recognized only the ordinary call shape, so it
skipped null-conditional calls and did not generate an interceptor.

### Description of Change
GetInvokedMethodName now recognizes both syntax types.
Therefore, ordinary and null-conditional SetBinding calls follow the
same interceptor-generation process.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes #38365 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/0c5a735d-4a06-4261-8488-f19f464a2be9"
>| <video
src="https://github.com/user-attachments/assets/b700be4b-776f-4def-bbd2-8325d1007bd1">|
kubaflo pushed a commit that referenced this pull request Oct 3, 2026
…#38651)

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

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
label?.SetBinding(...) should create a compiled binding when label is
not null.
Instead, MAUI did not generate the required interceptor, so the app
compiled but threw Call to SetBinding<...> was not intercepted at
runtime.

### Root Cause
label.SetBinding(...) and label?.SetBinding(...) have different C#
syntax-tree shapes.
The MAUI generator recognized only the ordinary call shape, so it
skipped null-conditional calls and did not generate an interceptor.

### Description of Change
GetInvokedMethodName now recognizes both syntax types.
Therefore, ordinary and null-conditional SetBinding calls follow the
same interceptor-generation process.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes #38365 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/0c5a735d-4a06-4261-8488-f19f464a2be9"
>| <video
src="https://github.com/user-attachments/assets/b700be4b-776f-4def-bbd2-8325d1007bd1">|
kubaflo pushed a commit that referenced this pull request Oct 3, 2026
…#38651)

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

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
label?.SetBinding(...) should create a compiled binding when label is
not null.
Instead, MAUI did not generate the required interceptor, so the app
compiled but threw Call to SetBinding<...> was not intercepted at
runtime.

### Root Cause
label.SetBinding(...) and label?.SetBinding(...) have different C#
syntax-tree shapes.
The MAUI generator recognized only the ordinary call shape, so it
skipped null-conditional calls and did not generate an interceptor.

### Description of Change
GetInvokedMethodName now recognizes both syntax types.
Therefore, ordinary and null-conditional SetBinding calls follow the
same interceptor-generation process.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes #38365 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/0c5a735d-4a06-4261-8488-f19f464a2be9"
>| <video
src="https://github.com/user-attachments/assets/b700be4b-776f-4def-bbd2-8325d1007bd1">|
kubaflo pushed a commit that referenced this pull request Oct 6, 2026
…#38651)

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

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
label?.SetBinding(...) should create a compiled binding when label is
not null.
Instead, MAUI did not generate the required interceptor, so the app
compiled but threw Call to SetBinding<...> was not intercepted at
runtime.

### Root Cause
label.SetBinding(...) and label?.SetBinding(...) have different C#
syntax-tree shapes.
The MAUI generator recognized only the ordinary call shape, so it
skipped null-conditional calls and did not generate an interceptor.

### Description of Change
GetInvokedMethodName now recognizes both syntax types.
Therefore, ordinary and null-conditional SetBinding calls follow the
same interceptor-generation process.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes #38365 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/0c5a735d-4a06-4261-8488-f19f464a2be9"
>| <video
src="https://github.com/user-attachments/assets/b700be4b-776f-4def-bbd2-8325d1007bd1">|
kubaflo pushed a commit that referenced this pull request Oct 6, 2026
…#38651)

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

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
label?.SetBinding(...) should create a compiled binding when label is
not null.
Instead, MAUI did not generate the required interceptor, so the app
compiled but threw Call to SetBinding<...> was not intercepted at
runtime.

### Root Cause
label.SetBinding(...) and label?.SetBinding(...) have different C#
syntax-tree shapes.
The MAUI generator recognized only the ordinary call shape, so it
skipped null-conditional calls and did not generate an interceptor.

### Description of Change
GetInvokedMethodName now recognizes both syntax types.
Therefore, ordinary and null-conditional SetBinding calls follow the
same interceptor-generation process.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes #38365 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/0c5a735d-4a06-4261-8488-f19f464a2be9"
>| <video
src="https://github.com/user-attachments/assets/b700be4b-776f-4def-bbd2-8325d1007bd1">|
kubaflo pushed a commit that referenced this pull request Oct 13, 2026
…#38651)

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

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
label?.SetBinding(...) should create a compiled binding when label is
not null.
Instead, MAUI did not generate the required interceptor, so the app
compiled but threw Call to SetBinding<...> was not intercepted at
runtime.

### Root Cause
label.SetBinding(...) and label?.SetBinding(...) have different C#
syntax-tree shapes.
The MAUI generator recognized only the ordinary call shape, so it
skipped null-conditional calls and did not generate an interceptor.

### Description of Change
GetInvokedMethodName now recognizes both syntax types.
Therefore, ordinary and null-conditional SetBinding calls follow the
same interceptor-generation process.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes #38365 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/0c5a735d-4a06-4261-8488-f19f464a2be9"
>| <video
src="https://github.com/user-attachments/assets/b700be4b-776f-4def-bbd2-8325d1007bd1">|
kubaflo pushed a commit that referenced this pull request Oct 13, 2026
…#38651)

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

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
label?.SetBinding(...) should create a compiled binding when label is
not null.
Instead, MAUI did not generate the required interceptor, so the app
compiled but threw Call to SetBinding<...> was not intercepted at
runtime.

### Root Cause
label.SetBinding(...) and label?.SetBinding(...) have different C#
syntax-tree shapes.
The MAUI generator recognized only the ordinary call shape, so it
skipped null-conditional calls and did not generate an interceptor.

### Description of Change
GetInvokedMethodName now recognizes both syntax types.
Therefore, ordinary and null-conditional SetBinding calls follow the
same interceptor-generation process.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes #38365 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/0c5a735d-4a06-4261-8488-f19f464a2be9"
>| <video
src="https://github.com/user-attachments/assets/b700be4b-776f-4def-bbd2-8325d1007bd1">|

This branch was successfully deployed

1 active deployment
copilot-pat-pool — 6b4b1f91 Deployed Sep 18, 2026 by Shalini-Ashokan via conclusion #1958
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration 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 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.

Compiled-binding interceptor not generated for SetBinding call wrapped in null-conditional access (?.)

6 participants