Skip to content

Feature: Improve diagnostic messages#19398

Merged
T-Gro merged 51 commits intomainfrom
feature/diagnostic-messages
Apr 20, 2026
Merged

Feature: Improve diagnostic messages#19398
T-Gro merged 51 commits intomainfrom
feature/diagnostic-messages

Conversation

@T-Gro
Copy link
Copy Markdown
Member

@T-Gro T-Gro commented Mar 6, 2026

Diagnostic message improvements

Batch of diagnostic message improvements addressing long-standing usability issues. Focus: clearer wording, actionable hints, and catching common beginner mistakes.

Issues fixed

Other improvements

  • FS0003: Shows concrete type when a non-function value is applied as a function, e.g. It has type int, which does not accept arguments.
  • FS0025: For-loop constant pattern hint via DiagnosticsLogger interceptor (no pipeline API change).
  • FS0039: Minor grammar improvement ("does not define a field, constructor, or member named").
  • FS0247: Namespace/module collision message now suggests renaming.
  • FS1 (tuple operator): Tuple-specific hint about unintended commas instead of generic "missing argument" advice.

T-Gro and others added 30 commits February 27, 2026 14:54
Replace academic jargon ('indeterminate type', 'program point') with
plain language that helps beginners understand what to do.

Old: Lookup on object of indeterminate type based on information prior
to this program point. A type annotation may be needed prior to this
program point to constrain the type of the object. This may allow the
lookup to be resolved.

New: The type of this expression could not be inferred before accessing
its members. Add a type annotation, e.g. (expr: SomeType), to constrain
the type before this point.

Fixes #13013

Co-authored-by: Copilot <[email protected]>
…phrasing

Change error message from:
  The type 'X' does not define the field, constructor or member 'Y'.
to:
  The type 'X' does not have a field, property, or member named 'Y'.

Fixes #13012

Co-authored-by: Copilot <[email protected]>
Replace the confusing 'Successive arguments should be separated by spaces
or tupled' message with a clearer, actionable message that tells the user
their argument expression needs parentheses and includes an example.

Fixes #6687

Co-authored-by: Copilot <[email protected]>
Append suggestion text to error FS0670 (tcNotSufficientlyGenericBecauseOfScope):
'Consider adding '\''inline'\'' to the member or function definition.'

Fixes #3302

Co-authored-by: Copilot <[email protected]>
…enFunction

When SRTP resolution fails with a function type, the error now suggests
the operator may not be in scope and advises checking module/namespace opens.

Fixes #2828

Co-authored-by: Copilot <[email protected]>
Change the error message from:
  'A namespace and a module named X both occur in two parts of this assembly'
to:
  'The name X is used as both a namespace and a module in this assembly. Rename one of them to avoid the conflict.'

Updated FSComp.txt, all 13 xlf localization files, and test baselines.

Co-authored-by: Copilot <[email protected]>
Pass exprTy (the expression's actual type) instead of overallTy.Commit
(the expected result type) to the NotAFunction exception. This ensures
the error message shows the concrete type of the value being applied,
not an unsolved type variable.

Use isTyparTy instead of string heuristic for type variable detection.
Add targeted tests for int, string, bool, record, and tuple types.

Co-authored-by: Copilot <[email protected]>
When a type provider is used with a wrong static parameter name, the error
now lists available parameter names (capped at 5) to help the user identify
the correct name.

Fixes #11445

Co-authored-by: Copilot <[email protected]>
When a for-loop uses a constant pattern (e.g., 'for 0 in 1..10'), the
incomplete match warning now includes the hint: 'Did you use a constant
where a loop variable was expected?'

The hint only applies to constant patterns in for-loops, not to DU
destructuring patterns like 'for Some x in ...' or regular match expressions.

Co-authored-by: Copilot <[email protected]>
…uple

When a type mismatch involves a tuple as the actual type, the error message
now says 'is a tuple of type' instead of 'but here has type', making it
clearer for beginners that the * syntax represents a tuple.

Fixes #11234

Co-authored-by: Copilot <[email protected]>
When an if/elif chain is missing the final else branch, the compiler now
produces the same quality error message as for a simple if without else:
'This if expression is missing an else branch...' instead of the generic
type mismatch error.

The fix detects elif chains missing a final else at two levels:
- At the outer if level, using a recursive elifChainMissingElse helper
- At the inner elif level, when the context is ElseBranchResult and
  synElseExprOpt is None, setting OmittedElseBranch with the correct
  range to match the UnifyTypes error location.

Fixes #6873

Co-authored-by: Copilot <[email protected]>
When a list literal contains exactly one tuple element (e.g. [1, 2, 3]),
emit warning FS3883 suggesting the use of semicolons instead of commas.
Parenthesized tuples [(1, 2, 3)] do not trigger the warning.

Fixes #1120

Co-authored-by: Copilot <[email protected]>
Change SynExpr.Tuple(_, _, _, _) to SynExpr.Tuple(false, _, _, _) so that
[struct(1, 2, 3)] does not trigger a false positive warning. Add test for
struct tuple case.

Co-authored-by: Copilot <[email protected]>
Add a component test that asserts the csExpectTypeWithOperatorButGivenFunction
error message includes the 'operator may not be in scope' hint text added
for #2828.

Co-authored-by: Copilot <[email protected]>
The catch-all arm in CompilerDiagnostics.fs (ErrorFromAddingTypeEquation2Tuple)
was untested. This test uses a recursive function producing a tuple that
triggers the ConstraintSolverInfiniteTypes inner error, which falls through
to the catch-all arm and verifies the 'but given a tuple of type' message.

Co-authored-by: Copilot <[email protected]>
Add doc comments to MatchIncomplete exception in .fsi and .fs explaining
all 4 fields including the isForLoopBinding flag. Add inline comment
(* isForLoopBinding: *) at the call site in CheckExpressions.fs and a
block comment explaining why the DiagnosticsLogger interception exists.

Co-authored-by: Copilot <[email protected]>
Extract typeEquationMessage helper to replace duplicated isAnyTupleTy
branching at both ErrorFromAddingTypeEquation match arms.

Co-authored-by: Copilot <[email protected]>
Replace nested match-within-match on env.eContextInfo and synElseExprOpt
with a single tuple match containing 5 flat arms. No behavioral change.

Co-authored-by: Copilot <[email protected]>
Exercise the branch in CompilerDiagnostics.fs where the type of the
value being applied is a type variable (isTyparTy), which falls back
to the plain 'not a function' message without displaying the type.

Uses a struct-constrained type parameter to prevent unification with
a function type, ensuring the NotAFunction error is raised with the
type still being a type variable.

Co-authored-by: Copilot <[email protected]>
Add three new tests to verify elif chains compile cleanly:
- Elif chain with final else (string return)
- Unit-returning elif chain without else
- Deep elif chain (4 branches) with final else

Co-authored-by: Copilot <[email protected]>
Extract formatAvailableNames helper from CrackStaticConstantArgs for
diagnostic formatting of static parameter names truncated to 5 entries.
Add 5 unit tests covering empty, single, fewer than 5, exactly 5, and
more than 5 names.

Co-authored-by: Copilot <[email protected]>
- Extract markForLoopMatchIncomplete helper to reduce nesting in TcForEachExpr
- Add inline comments for NotAFunction vs NotAFunctionButIndexer type asymmetry
- Add inline comments for OmittedElseBranch range choices in elif context
- Add operator scope hint to csExpectTypeWithOperatorButGivenTuple for consistency
- Make formatAvailableNames internal (val internal in .fsi)
- Add cross-reference comments for sibling parser error-recovery rules
- Add component tests for FS0597 (successive arguments)
- Update neg60.bsl baseline for tuple operator hint

Co-authored-by: Copilot <[email protected]>
…ead isForLoopBinding

- formatAvailableNames: append '...' when list is truncated beyond 5
- Warning 3883 (comma vs semicolon in lists): downgrade from warning to
  informationalWarning to reduce false positive impact
- Remove diagnostic interception pattern (markForLoopMatchIncomplete):
  thread isForLoopBinding through CompilePattern call chain instead of
  post-hoc exception rewriting via DiagnosticsLogger wrapper

Co-authored-by: Copilot <[email protected]>
- FS3883 tests: Warning -> Information (intentionally info-level)
- formatAvailableNames test: update expected value to include ellipsis
- FSComp.txt: add trailing newline
- Add negative test: type alias for tuple still says 'is a tuple of type'

Co-authored-by: Copilot <[email protected]>
… test coverage

- Convert elifChainMissingElse from recursive to iterative while-loop
  to avoid theoretical stack overflow on deep elif chains
- Expand isConstantPattern to handle SynPat.Typed(SynPat.Const) and
  SynPat.Paren(SynPat.Typed(SynPat.Const)) for typed for-loop constants
- Add SRTP tuple operator scope hint test (csExpectTypeWithOperatorButGivenTuple)
- Add struct tuple type mismatch test verifying 'is a tuple of type' phrasing

Co-authored-by: Copilot <[email protected]>
…, restore constructor in FS0039

- csExpectTypeWithOperatorButGivenTuple: Replace copy-pasted 'missing
  argument to a function' hint with tuple-specific guidance about
  unintended commas creating tuples
- FS0670: Broaden 'Consider adding inline' to also suggest type
  annotations and converting values to functions (inline is wrong
  for value restriction and mutable capture cases)
- FS0039: Restore 'constructor' in the message so DU case typos
  like Shape.Circl hint that union cases exist

Co-authored-by: Copilot <[email protected]>
@T-Gro T-Gro marked this pull request as ready for review April 1, 2026 10:03
@T-Gro T-Gro requested a review from a team as a code owner April 1, 2026 10:03
@T-Gro T-Gro changed the title WIP: Feature: Improve diagnostic messages Feature: Improve diagnostic messages Apr 1, 2026
Comment thread docs/release-notes/.FSharp.Compiler.Service/11.0.100.md Outdated
Copilot AI requested a review from abonie April 14, 2026 19:10
@T-Gro T-Gro requested a review from abonie April 16, 2026 07:29
@T-Gro T-Gro merged commit 2535da2 into main Apr 20, 2026
47 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in F# Compiler and Tooling Apr 20, 2026
@T-Gro T-Gro deleted the feature/diagnostic-messages branch April 20, 2026 20:35
T-Gro added a commit that referenced this pull request Apr 21, 2026
…ostics

- Resolve FSComp.txt number conflict: bump PR entry to 3887 (later removed by LexFilter commit)
- Resolve XLF conflicts: keep main's parsLetBangCannotBeLastInCE and tcListLiteralWithSingleTupleElement entries
- Update Auto property 08/09/10 baselines for improved diagnostic messages from main (#19398)

Co-authored-by: Copilot <[email protected]>
T-Gro added a commit that referenced this pull request Apr 28, 2026
Merge gusty's latest tuple parsing, diagnostic messages (#19398),
and baseline refresh (#19603) into PR 19396.

Fix: Change deeply nested FSharpFunc SRTP test (|>>>> map cubed)
from shouldSucceed to shouldFail with Error 73. Three levels of
SRTP nesting on FSharpFunc triggers 'Undefined or unsolved type
variable' ICE in IlxGen. Two levels (|>>>) works. This is a known
limitation also present on PR 19602.

Co-authored-by: Copilot <[email protected]>
T-Gro added a commit that referenced this pull request Apr 30, 2026
Merge gusty/feature-operators-extensions: tuple parsing, #19398
diagnostic messages, #19603 baseline refresh.

Fix: IlxGen Error 73 'Undefined or unsolved type variable: _arg6'
in closure codegen for deeply nested SRTP on function types.

Root cause: GetIlxClosureFreeVars computed closure free type vars
from accFreeInExpr, then generated witness infos from those typars.
But witness types (from GetTraitWitnessInfosOfTypars) can reference
additional type variables not captured by the expression's free vars.
When FreeVarStorageForWitnessInfos called GenType on the witness type,
it hit a typar not in the TypeReprEnv.

Fix: compute witness infos BEFORE creating the inner TypeReprEnv,
collect extra type variables from witness types, and include them
in cloFreeTyvars so they're declared as IL type parameters on the
closure type.

Co-authored-by: Copilot <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment