Skip to content

Raise empty local functions - #3902

Open
richlander wants to merge 4 commits into
mainfrom
fix/3666-empty-local-functions
Open

Raise empty local functions#3902
richlander wants to merge 4 commits into
mainfrom
fix/3666-empty-local-functions

Conversation

@richlander

@richlander richlander commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Change

Should we accept this change?

Conclusion: PASS — the pass now recognizes the compiler witness for an empty void body, while failed imports and empty non-void bodies remain declined.

Benchmark target

Benchmark target: ILInspector.Decompiler.Tests.dll at 0ff89bef / c278e72c

dotnet-inspect member ILInspector.Decompiler.Tests.UnraisedLocalFunctionSamples \
  --library artifacts/bin/ILInspector.Decompiler.Tests/release/ILInspector.Decompiler.Tests.dll \
  -m CallsEmpty -S "Decompiled Source" --plaintext --bare

Original source

public static void CallsEmpty()
{
    F();
    static void F() { }
}

Before

public static void CallsEmpty() => __CallsEmpty_g__F_0_0();
  • Valid: False (CS0103)
  • Correct: False; the isolated method does not compile
  • IL fidelity: not currently checkable; compile-back fails at binding
  • Taste applied: None
  • Commit: 0ff89bef

After

public static void CallsEmpty()
{
    F();
    return;
    static void F()
    {
    }
}
  • Valid: True
  • Correct: True
  • IL fidelity: True (Exact, contract v3)
  • Taste applied: None
  • Commit: c278e72c

Fully raised

The After decompilation is in the fully raised state.

Evidence

Check Baseline Head
Product build Pass Pass
Focused local-function tests 31 passed, 1 expected red 32 passed
Decompiler fast suite 4,605 passed, 1 expected red, 8 skipped 4,606 passed, 8 skipped
Pre-merge fidelity gates Not run 86 passed
Fixture compile-back 1 Exact; CallsEmpty + 2 existing declined cases fail CS0103 2 Exact; only the 2 existing declined cases fail CS0103
Real witness CallsEmpty is an undeclared synthesized call at Partial Empty F declaration at Full and Exact

The positive test failed before the product change. Close negatives prove a missing import and a synthetic empty non-void body with the same terminal-null-return shape remain declined at Partial.

Decompiler quality

Should the corpus signal block this PR?

Conclusion: PASS — the exact-base PR quick card reports no regression.

PR quick gate

Run: hash-stable 100 methods per assembly, 15 assemblies, 1,500 methods.

Metric (goal) Baseline PR
Detected lowering residue (-) 238 (15.87%) 237 (15.80%)
Conditional-branch residue (-) 44 (2.93%) 43 (2.87%)
Forward-merge stops (-) 28 (1.87%) 27 (1.80%)
Pass bugs (-) 0 0

The six delta rows are three added and three removed hash-sample identities in generated/self assemblies; there are no changed common-method rows.

Render A/B

42,210 methods evaluated; 0 changed, 0 added, 0 removed, and no regressions. This sensor has no cross-method import seam, so it is population regression evidence rather than the positive local-function witness; the seam-enabled fixture and compile-back result prove the behavior change.

Validation

  • dotnet build dotnet-inspect.slnx -c Release
  • dotnet run --project src/ILInspector.Decompiler.Tests -c Release -- -class ILInspector.Decompiler.Tests.UnraisedLocalFunctionCallTests -noColor
  • dotnet run --project src/ILInspector.Decompiler.Tests -c Release -- --gate fast -noColor
  • dotnet run --project src/ILInspector.Decompiler.Tests -c Release -- --gate pre-merge -noColor
  • Focused DecompilerHarness --fidelity-check
  • Exact-base DecompilerHarness --render-ab
  • Exact-base PR quick corpus card

richlander and others added 3 commits August 8, 2026 10:41
Accept the compiler-produced terminal void return as a printable empty local-function body and remove it before emitting the declaration. Keep missing imports and malformed empty non-void bodies declined.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep a terminal null return in the synthetic non-void body and assert that shape survives the nested pipeline, so the negative reaches the new void-only acceptance branch.

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

Copy link
Copy Markdown
Owner Author

Head moved to cbcac58e: a5673a06 fixes Gemini Pro's round-one test-vacuity finding, and merge commit cbcac58e integrates origin/main at 6a73ea68. Round two will review this new exact head after CI is terminal-green.

@richlander

Copy link
Copy Markdown
Owner Author

Round-two test-windows failed in unrelated EvilPoolSweepGateTests.ASweepLeavesNoTemporaryWhenAWriteFailsAfterCreatingOne: concurrent Debug compilation could not write locked InertText, ILInspector.Findings, and ILInspector.MetadataPrimitives outputs (CS2012, locked by VBCSCompiler). The same 4,614-test fast lane passed locally on Windows at cbcac58e; rerunning only the failed CI job without moving the head.

@richlander

Copy link
Copy Markdown
Owner Author

Head moved to c278e72c: merge commit integrates origin/main at 0ff89bef. All local evidence was regenerated against that exact base; round two remains gated until this new head is mergeable and ci-required is green.

@richlander

Copy link
Copy Markdown
Owner Author

Adversarial review reconciliation

Exact reviewed head: c278e72c60adeb124e82d9cdf8eb9a3586077ae6
Exact base: 0ff89befc5c822bbba863b8bdbd75d2447ca7d3b

Round 1 (a1af8745)

  • Claude Opus 5: clean. Focused local-function class passed 32/32; implementation, candidate ownership, null-import separation, and Full/Exact claims held.
  • Gemini 3.1 Pro: found that EmptyNonVoidImportedBody_RemainsDeclined used a zero-statement body and therefore did not exercise the new Return(null) + IsVoid discriminator.
  • Resolution: a5673a06 now preserves exactly one block containing Return(null) under the original int signature and asserts that exact shape survives the nested pipeline while the candidate remains declined at Partial.
  • Explicit non-action: Gemini also requested another no-seam case for CallsEmpty; the existing EmptyLocalFunction_WhenBodyImportFails_RemainsDeclined already exercises that exact host with an import seam returning null and asserts no declaration, synthesized fallback spelling, and Partial. The general no-seam sweep remains covered by WithoutTheCrossMethodSeam_EveryCallIsStampedAndFidelityDegrades.

Round 2 (c278e72c)

  • Claude Opus 5: clean. Focused class passed 32/32; related local-function/lambda set passed 88/88. Mutation probes confirmed the corrected non-void negative fails if the IsVoid guard is removed and the positive fails if terminal-return detachment is removed.
  • Gemini 3.1 Pro: clean. Confirmed the prior finding is fully resolved: the retained int-signature [Return(null)] body reaches the new discriminator, remains intact because the candidate declines, and cannot pass through an earlier unrelated gate.

Both required model families are clean at the fixed head. CI is green at that head, including ci-required and decompiler-gates.

@richlander
richlander marked this pull request as ready for review August 8, 2026 22:17
@richlander

Copy link
Copy Markdown
Owner Author

Ready to merge

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Empty local functions are never raised: IsPrintableBody rejects a zero-statement body

1 participant