Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Type-check formatted interpolation holes only once
The formatted/aligned hole path lowered via a synthesized String.Format that
re-type-checked the hole expression a second time, which could duplicate an
error in the hole and leak a spurious 'Format' overload-resolution error. Bind
the already-checked, boxed hole value to a temporary and reference that from the
synthesized String.Format instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
  • Loading branch information
charlesroddie and claude committed Jun 20, 2026
commit e83ddfb6f1636cedd5e49d813cd00687c4d32f29
12 changes: 10 additions & 2 deletions src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7604,8 +7604,16 @@ and TcInterpolatedStringViaConcat (cenv: cenv, overallTy: OverallTy, env: TcEnv,
match alignment, format with
| None, None -> (if isStringTy g fillTy then (fill, true) else (mkCallStringOperator g m fillTy fill, false)), tpenv
| _ ->
let arg, tpenv = TcExpr cenv (MustEqual g.string_ty) env tpenv (stringFormatOp (alignment, format, synFill))
(arg, false), tpenv
// Format the already-checked hole via a synthesized 'String.Format', binding its boxed value
// to a temporary so the hole is not type-checked a second time. Re-checking 'synFill' would
// duplicate any error in it; boxing to 'obj' keeps the 'Format' overload unambiguous (so a
// hole that already failed to check doesn't also leak a confusing 'Format' overload error).
let boxedFill = mkCallBox g m fillTy fill
let tmpVal, _ = mkLocal mSynth "interpHole" (tyOfExpr g boxedFill)
let envInner = AddLocalVal g cenv.tcSink mSynth tmpVal env
let tmpRef = SynExpr.Ident(mkSynId mSynth tmpVal.LogicalName)
Comment thread
charlesroddie marked this conversation as resolved.
Resolved
let arg, tpenv = TcExpr cenv (MustEqual g.string_ty) envInner tpenv (stringFormatOp (alignment, format, tmpRef))
(mkCompGenLet mSynth tmpVal boxedFill arg, false), tpenv

// One (string expression, may-be-null) per non-empty part; a builder (not map) since 'tpenv' threads
// through the holes. Literals and conversions are never null; only a raw string passthrough may be.
Expand Down
Loading