Skip to content

Fix #16419 at source: avoid double type-check of seq implicit-yield body#19895

Open
T-Gro wants to merge 2 commits into
dotnet:mainfrom
T-Gro:fix/16419-source-root-cause
Open

Fix #16419 at source: avoid double type-check of seq implicit-yield body#19895
T-Gro wants to merge 2 commits into
dotnet:mainfrom
T-Gro:fix/16419-source-root-cause

Conversation

@T-Gro
Copy link
Copy Markdown
Member

@T-Gro T-Gro commented Jun 5, 2026

Description

Follow-up to #19791 addressing @auduchinok's review: the sink-level HashSet dedup hid the real bug.

In the implicit-yield branch of tcSequenceExprBodyAsSequenceOrStatement, the body of seq { e } was type-checked twice — once via TryTcStmt (probe: is it unit?) and again via TcExprFlex (as a yielded element). Both passes ran every side-effecting check inside TcExpr (format-string parsing, name-resolution notifications, …), which is why a single %d in seq { sprintf "%d" 1 } produced two sink entries.

The second pass now reuses the existing env.eCachedImplicitYieldExpressions cache that TcExprSequentialOrImplicitYield already uses for the e1; e2 shape, so the body is checked once. The HashSet workaround in TcResultsSinkImpl is reverted.

Fixes #16419.

Checklist

  • Test cases added
  • Performance benchmarks added in case of performance changes
  • Release notes entry updated

…ield body

Replace the HashSet-based dedup in TcResultsSinkImpl.NotifyFormatSpecifierLocation
(added in PR dotnet#19791) with a fix at the source.

In CheckSequenceExpressions.fs, the implicit-yield branch of
tcSequenceExprBodyAsSequenceOrStatement first calls TryTcStmt to detect
whether the body has type unit, then (if not) calls TcExprFlex on the
same SynExpr a second time to type-check it as a yielded expression.
Both passes invoke side-effecting checks like format-string parsing,
which is why printf format-specifier sink notifications, name-resolution
events, etc., were doubled for a single sub-expression.

Reuse the existing eCachedImplicitYieldExpressions HashMultiMap (already
used by TcExprSequentialOrImplicitYield for the e1; e2 case) so the
second type-check returns the cached result from the first pass instead
of re-running it. TcExprThen looks up by SynExpr.Range gated by
obj.ReferenceEquals, so identity is preserved and there is no risk of
cross-expression collisions.

Tests in EditorTests.fs verify:
- single format-specifier emission for seq { sprintf "%d" 1 },
  seq { sprintf "%d %s %A" 1 "x" 2 }, seq { printfn "%d" 1 }
- expected-type-driven inference is preserved: subsumption int->obj
  and string->obj, nullness flex, overload resolution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

❗ Release notes required


✅ Found changes and release notes in following paths:

Warning

No PR link found in some release notes, please consider adding it.

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/11.0.100.md No current pull request URL (#19895) found, please consider adding it

@T-Gro T-Gro requested a review from abonie June 5, 2026 09:06
@T-Gro T-Gro enabled auto-merge (squash) June 5, 2026 09:06
…ndCheck helpers

- Add module-level testFile constant (Path.Combine for OS neutrality)
- Add parseAndCheck helper so each test no longer re-declares the file
- Collapse the 3 format-specifier and 4 implicit-yield tests added in the
  previous commit into 2 Theory tests driven by InlineData

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added the AI-Tooling-Check-Scanned-Clean Tooling check: diff analyzed, no interesting infrastructure files label Jun 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-Tooling-Check-Scanned-Clean Tooling check: diff analyzed, no interesting infrastructure files

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

GetFormatSpecifierLocationsAndArity returns duplicated entries in computation expressions

1 participant