Skip to content

Commit 2535da2

Browse files
authored
Feature: Improve diagnostic messages (#19398)
1 parent 9b0021e commit 2535da2

120 files changed

Lines changed: 1380 additions & 370 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,8 @@
4040
* Added warning FS3884 when a function or delegate value is used as an interpolated string argument. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289))
4141
* Add `#version;;` directive to F# Interactive to display version and environment information. ([Issue #13307](https://github.com/dotnet/fsharp/issues/13307), [PR #19332](https://github.com/dotnet/fsharp/pull/19332))
4242

43+
### Changed
44+
45+
* Improvements in error and warning messages: new error FS3885 when `let!`/`use!` is the final expression in a computation expression; new warning FS3886 when a list literal contains a single tuple element (likely missing `;` separator); improved wording for FS0003, FS0025, FS0039, FS0072, FS0247, FS0597, FS0670, FS3082, and SRTP operator-not-in-scope hints. ([PR #19398](https://github.com/dotnet/fsharp/pull/19398))
46+
47+
### Breaking Changes

src/Compiler/Checking/Expressions/CheckExpressions.fs

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4118,6 +4118,12 @@ type ImplicitlyBoundTyparsAllowed =
41184118
| NewTyparsOK
41194119
| NoNewTypars
41204120

4121+
/// Formats a list of names for display in diagnostics, truncating to at most 5 entries.
4122+
let formatAvailableNames (names: string array) =
4123+
let truncated = names |> Array.truncate 5
4124+
let result = truncated |> String.concat ", "
4125+
if names.Length > 5 then result + ", ..." else result
4126+
41214127
//-------------------------------------------------------------------------
41224128
// Checking types and type constraints
41234129
//-------------------------------------------------------------------------
@@ -5022,7 +5028,12 @@ and CrackStaticConstantArgs (cenv: cenv) env tpenv (staticParameters: Tainted<Pr
50225028
if staticParameters |> Array.exists (fun sp -> n.idText = sp.PUntaint((fun sp -> sp.Name), n.idRange)) then
50235029
error (Error(FSComp.SR.etStaticParameterAlreadyHasValue n.idText, n.idRange))
50245030
else
5025-
error (Error(FSComp.SR.etNoStaticParameterWithName n.idText, n.idRange))
5031+
let availableNames =
5032+
staticParameters
5033+
|> Array.map (fun sp -> sp.PUntaint((fun sp -> sp.Name), n.idRange))
5034+
|> formatAvailableNames
5035+
5036+
error (Error(FSComp.SR.etNoStaticParameterWithName (n.idText, availableNames), n.idRange))
50265037
| [_] -> ()
50275038
| _ -> error (Error(FSComp.SR.etMultipleStaticParameterWithName n.idText, n.idRange))
50285039

@@ -6162,6 +6173,7 @@ and TcExprUndelayed (cenv: cenv) (overallTy: OverallTy) env tpenv (synExpr: SynE
61626173
| SynExpr.MatchBang (trivia = { MatchBangKeyword = m })
61636174
| SynExpr.WhileBang (range = m) ->
61646175
error(Error(FSComp.SR.tcConstructRequiresComputationExpression(), m))
6176+
61656177
| SynExpr.IndexFromEnd (rightExpr, m) ->
61666178
errorR(Error(FSComp.SR.tcTraitInvocationShouldUseTick(), m))
61676179
let adjustedExpr = ParseHelpers.adjustHatPrefixToTyparLookup m rightExpr
@@ -6294,6 +6306,11 @@ and TcExprTuple (cenv: cenv) overallTy env tpenv (isExplicitStruct, args, m) =
62946306
and TcExprArrayOrList (cenv: cenv) overallTy env tpenv (isArray, args, m) =
62956307
let g = cenv.g
62966308

6309+
match isArray, args with
6310+
| false, [ SynExpr.Tuple(false, _, _, _) ] ->
6311+
informationalWarning (Error(FSComp.SR.tcListLiteralWithSingleTupleElement (), m))
6312+
| _ -> ()
6313+
62976314
CallExprHasTypeSink cenv.tcSink (m, env.NameEnv, overallTy.Commit, env.AccessRights)
62986315
let argTy = NewInferenceType g
62996316
let actualTy = if isArray then mkArrayType g argTy else mkListTy g argTy
@@ -8295,11 +8312,40 @@ and TcForEachExpr cenv overallTy env tpenv (seqExprOnly, isFromSource, synPat, s
82958312
// Add the pattern match compilation
82968313
let bodyExpr =
82978314
let valsDefinedByMatching = ListSet.remove valEq elemVar vspecs
8298-
CompilePatternForMatch
8299-
cenv env synEnumExpr.Range pat.Range false IgnoreWithWarning (elemVar, [], None)
8300-
[MatchClause(pat, None, TTarget(valsDefinedByMatching, bodyExpr, None), mIn)]
8301-
enumElemTy
8302-
overallTy.Commit
8315+
8316+
let isConstantPattern =
8317+
match synPat with
8318+
| SynPat.Const _
8319+
| SynPat.Paren(SynPat.Const _, _)
8320+
| SynPat.Typed(SynPat.Const _, _, _)
8321+
| SynPat.Paren(SynPat.Typed(SynPat.Const _, _, _), _) -> true
8322+
| _ -> false
8323+
8324+
let compileMatch () =
8325+
CompilePatternForMatch
8326+
cenv env synEnumExpr.Range pat.Range false IgnoreWithWarning (elemVar, [], None)
8327+
[MatchClause(pat, None, TTarget(valsDefinedByMatching, bodyExpr, None), mIn)]
8328+
enumElemTy
8329+
overallTy.Commit
8330+
8331+
if isConstantPattern then
8332+
use _ =
8333+
UseTransformedDiagnosticsLogger(fun oldLogger ->
8334+
{ new DiagnosticsLogger("forLoopConstantHint") with
8335+
member _.DiagnosticSink(diagnostic) =
8336+
match diagnostic.Exception with
8337+
| MatchIncomplete _ ->
8338+
oldLogger.DiagnosticSink(
8339+
{ diagnostic with
8340+
Exception = MatchIncompleteForLoopHint(diagnostic.Exception) })
8341+
| _ -> oldLogger.DiagnosticSink(diagnostic)
8342+
8343+
member _.ErrorCount = oldLogger.ErrorCount
8344+
})
8345+
8346+
compileMatch ()
8347+
else
8348+
compileMatch ()
83038349

83048350
// Apply the fixup to bind the elemVar if needed
83058351
let bodyExpr = bodyExprFixup elemVar bodyExpr
@@ -8466,15 +8512,17 @@ and Propagate (cenv: cenv) (overallTy: OverallTy) (env: TcEnv) tpenv (expr: Appl
84668512
else
84678513
if IsIndexerType g cenv.amap expr.Type then
84688514
let old = not (g.langVersion.SupportsFeature LanguageFeature.IndexerNotationWithoutDot)
8515+
// NotAFunctionButIndexer uses overallTy (expected type) for the indexer suggestion message.
84698516
error (NotAFunctionButIndexer(denv, overallTy.Commit, vName, mExpr, mArg, old))
84708517
else
8471-
error (NotAFunction(denv, overallTy.Commit, mExpr, mArg))
8518+
// NotAFunction uses exprTy (actual type) to show "has type X, which does not accept arguments".
8519+
error (NotAFunction(denv, exprTy, mExpr, mArg))
84728520

84738521
// f x (where 'f' is not a function)
84748522
| _ ->
84758523
// 'delayed' is about to be dropped on the floor, first do rudimentary checking to get name resolutions in its body
84768524
RecordNameAndTypeResolutionsDelayed cenv env tpenv delayed
8477-
error (NotAFunction(denv, overallTy.Commit, mExpr, mArg))
8525+
error (NotAFunction(denv, exprTy, mExpr, mArg))
84788526

84798527
propagate false delayed expr.Range exprTy
84808528

@@ -8795,7 +8843,7 @@ and TcApplicationThen (cenv: cenv) (overallTy: OverallTy) env tpenv mExprAndArg
87958843
TcDelayed cenv overallTy env tpenv mExprAndArg (MakeApplicableExprNoFlex cenv bodyOfCompExpr) (tyOfExpr g bodyOfCompExpr) ExprAtomicFlag.NonAtomic delayed
87968844

87978845
| _ ->
8798-
error (NotAFunction(denv, overallTy.Commit, mLeftExpr, mArg))
8846+
error (NotAFunction(denv, exprTy, mLeftExpr, mArg))
87998847

88008848
//-------------------------------------------------------------------------
88018849
// TcLongIdentThen: Typecheck "A.B.C<D>.E.F ... " constructs
@@ -9358,7 +9406,6 @@ and TcImplicitOpItemThen (cenv: cenv) overallTy env id sln tpenv mItem delayed =
93589406
| SynExpr.YieldOrReturn _
93599407
| SynExpr.YieldOrReturnFrom _
93609408
| SynExpr.MatchBang _
9361-
| LetOrUse(_, true, _)
93629409
| SynExpr.DoBang _
93639410
| SynExpr.WhileBang _
93649411
| SynExpr.TraitCall _
@@ -10773,6 +10820,13 @@ and CheckRecursiveBindingIds binds =
1077310820
if nm <> "" && not (hashOfBinds.Add nm) then
1077410821
error(Duplicate("value", nm, m))
1077510822

10823+
/// Returns true if the expression is an elif chain that ends without a final 'else' branch.
10824+
and elifChainMissingElse expr =
10825+
match expr with
10826+
| SynExpr.IfThenElse(elseExpr = None) -> true
10827+
| SynExpr.IfThenElse(elseExpr = Some elseExpr) -> elifChainMissingElse elseExpr
10828+
| _ -> false
10829+
1077610830
/// Process a sequence of sequentials mixed with iterated lets "let ... in let ... in ..." in a tail recursive way
1077710831
/// This avoids stack overflow on really large "let" and "letrec" lists
1077810832
and TcLinearExprs bodyChecker cenv env overallTy tpenv isCompExpr synExpr cont =
@@ -10819,12 +10873,15 @@ and TcLinearExprs bodyChecker cenv env overallTy tpenv isCompExpr synExpr cont =
1081910873
let env = { env with eIsControlFlow = true }
1082010874
let thenExpr, tpenv =
1082110875
let env =
10822-
match env.eContextInfo with
10823-
| ContextInfo.ElseBranchResult _ -> { env with eContextInfo = ContextInfo.ElseBranchResult synThenExpr.Range }
10824-
| _ ->
10825-
match synElseExprOpt with
10826-
| None -> { env with eContextInfo = ContextInfo.OmittedElseBranch synThenExpr.Range }
10827-
| _ -> { env with eContextInfo = ContextInfo.IfExpression synThenExpr.Range }
10876+
match env.eContextInfo, synElseExprOpt with
10877+
// Inside an elif chain with no final else: use full elif range (m) so the error points at the whole elif.
10878+
| ContextInfo.ElseBranchResult _, None -> { env with eContextInfo = ContextInfo.OmittedElseBranch m }
10879+
| ContextInfo.ElseBranchResult _, _ -> { env with eContextInfo = ContextInfo.ElseBranchResult synThenExpr.Range }
10880+
// Top-level if with no else: use then-branch range.
10881+
| _, None -> { env with eContextInfo = ContextInfo.OmittedElseBranch synThenExpr.Range }
10882+
| _, Some elseExpr when elifChainMissingElse elseExpr ->
10883+
{ env with eContextInfo = ContextInfo.OmittedElseBranch synThenExpr.Range }
10884+
| _ -> { env with eContextInfo = ContextInfo.IfExpression synThenExpr.Range }
1082810885

1082910886
if not isRecovery && Option.isNone synElseExprOpt then
1083010887
UnifyTypes cenv env m g.unit_ty overallTy.Commit

src/Compiler/Checking/Expressions/CheckExpressions.fsi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ type ImplicitlyBoundTyparsAllowed =
135135
| NewTyparsOK
136136
| NoNewTypars
137137

138+
/// Formats a list of names for display in diagnostics, truncating to at most 5 entries.
139+
val internal formatAvailableNames: names: string array -> string
140+
138141
//-------------------------------------------------------------------------
139142
// The rest are all helpers needed for declaration checking (CheckDeclarations.fs)
140143
//-------------------------------------------------------------------------

src/Compiler/Checking/PatternMatchCompilation.fs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@ open FSharp.Compiler.TypedTreeOps
2424
open FSharp.Compiler.TypeRelations
2525
open type System.MemoryExtensions
2626

27+
/// Exception raised when a pattern match is incomplete.
28+
/// Fields: isComputationExpression * (counterExample * isShownAsFieldPattern) option * range
2729
exception MatchIncomplete of bool * (string * bool) option * range
30+
31+
/// Wrapper that adds a for-loop hint to an existing MatchIncomplete diagnostic.
32+
exception MatchIncompleteForLoopHint of exn
33+
2834
exception RuleNeverMatched of range
2935
exception EnumMatchIncomplete of bool * (string * bool) option * range
3036

src/Compiler/Checking/PatternMatchCompilation.fsi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,13 @@ val internal CompilePattern:
7171
TType ->
7272
DecisionTree * DecisionTreeTarget list
7373

74+
/// Exception raised when a pattern match is incomplete.
75+
/// Fields: isComputationExpression * (counterExample * isShownAsFieldPattern) option * range
7476
exception internal MatchIncomplete of bool * (string * bool) option * range
7577

78+
/// Wrapper that adds a for-loop hint to an existing MatchIncomplete diagnostic.
79+
exception internal MatchIncompleteForLoopHint of exn
80+
7681
exception internal RuleNeverMatched of range
7782

7883
exception internal EnumMatchIncomplete of bool * (string * bool) option * range

src/Compiler/Driver/CompilerDiagnostics.fs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ type Exception with
126126
| InterfaceNotRevealed(_, _, m)
127127
| WrappedError(_, m)
128128
| PatternMatchCompilation.MatchIncomplete(_, _, m)
129+
| PatternMatchCompilation.MatchIncompleteForLoopHint(PatternMatchCompilation.MatchIncomplete(_, _, m))
129130
| PatternMatchCompilation.EnumMatchIncomplete(_, _, m)
130131
| PatternMatchCompilation.RuleNeverMatched m
131132
| ValNotMutable(_, _, m)
@@ -238,6 +239,7 @@ type Exception with
238239
| NameClash _ -> 23
239240
// 24 cannot be reused
240241
| PatternMatchCompilation.MatchIncomplete _ -> 25
242+
| PatternMatchCompilation.MatchIncompleteForLoopHint _ -> 25
241243
| PatternMatchCompilation.RuleNeverMatched _ -> 26
242244

243245
| ValNotMutable _ -> 27
@@ -449,7 +451,9 @@ module OldStyleMessages =
449451
let ConstraintSolverTypesNotInEqualityRelation2E () = Message("ConstraintSolverTypesNotInEqualityRelation2", "%s%s")
450452
let ConstraintSolverTypesNotInSubsumptionRelationE () = Message("ConstraintSolverTypesNotInSubsumptionRelation", "%s%s%s")
451453
let ErrorFromAddingTypeEquation1E () = Message("ErrorFromAddingTypeEquation1", "%s%s%s")
454+
let ErrorFromAddingTypeEquation1TupleE () = Message("ErrorFromAddingTypeEquation1Tuple", "%s%s%s")
452455
let ErrorFromAddingTypeEquation2E () = Message("ErrorFromAddingTypeEquation2", "%s%s%s")
456+
let ErrorFromAddingTypeEquation2TupleE () = Message("ErrorFromAddingTypeEquation2Tuple", "%s%s%s")
453457
let ErrorFromAddingTypeEquationTuplesE () = Message("ErrorFromAddingTypeEquationTuples", "%d%s%d%s%s")
454458
let ErrorFromApplyingDefault1E () = Message("ErrorFromApplyingDefault1", "%s")
455459
let ErrorFromApplyingDefault2E () = Message("ErrorFromApplyingDefault2", "")
@@ -564,6 +568,7 @@ module OldStyleMessages =
564568
let MatchIncomplete2E () = Message("MatchIncomplete2", "%s")
565569
let MatchIncomplete3E () = Message("MatchIncomplete3", "%s")
566570
let MatchIncomplete4E () = Message("MatchIncomplete4", "")
571+
let MatchIncompleteForLoopE () = Message("MatchIncompleteForLoop", "")
567572
let RuleNeverMatchedE () = Message("RuleNeverMatched", "")
568573
let EnumMatchIncomplete1E () = Message("EnumMatchIncomplete1", "")
569574
let ValNotMutableE () = Message("ValNotMutable", "%s")
@@ -657,6 +662,8 @@ type Exception with
657662

658663
member exn.Output(os: StringBuilder, suggestNames) =
659664

665+
let typeEquationMessage g ty2 normalE tupleE = if isAnyTupleTy g ty2 then tupleE else normalE
666+
660667
match exn with
661668
// TODO: this is now unused...?
662669
| ConstraintSolverTupleDiffLengths(_, _, tl1, tl2, m, m2) ->
@@ -764,17 +771,20 @@ type Exception with
764771
| ErrorFromAddingTypeEquation(g, denv, ty1, ty2, ConstraintSolverTypesNotInEqualityRelation(_, ty1b, ty2b, m, _, contextInfo), _) when
765772
typeEquiv g ty1 ty1b && typeEquiv g ty2 ty2b
766773
->
774+
let typeEquation1E =
775+
typeEquationMessage g ty2 ErrorFromAddingTypeEquation1E ErrorFromAddingTypeEquation1TupleE
776+
767777
let ty1, ty2, tpcs = NicePrint.minimalStringsOfTwoTypes denv ty1 ty2
768778

769779
OutputTypesNotInEqualityRelationContextInfo contextInfo ty1 ty2 m os (fun contextInfo ->
770780
match contextInfo with
771781
| ContextInfo.TupleInRecordFields ->
772-
os.AppendString(ErrorFromAddingTypeEquation1E().Format ty2 ty1 tpcs)
782+
os.AppendString(typeEquation1E().Format ty2 ty1 tpcs)
773783
os.AppendString(Environment.NewLine + FSComp.SR.commaInsteadOfSemicolonInRecord ())
774784
| _ when ty2 = "bool" && ty1.EndsWithOrdinal(" ref") ->
775-
os.AppendString(ErrorFromAddingTypeEquation1E().Format ty2 ty1 tpcs)
785+
os.AppendString(typeEquation1E().Format ty2 ty1 tpcs)
776786
os.AppendString(Environment.NewLine + FSComp.SR.derefInsteadOfNot ())
777-
| _ -> os.AppendString(ErrorFromAddingTypeEquation1E().Format ty2 ty1 tpcs))
787+
| _ -> os.AppendString(typeEquation1E().Format ty2 ty1 tpcs))
778788

779789
| ErrorFromAddingTypeEquation(_, _, _, _, (ConstraintSolverTypesNotInEqualityRelation(_, _, _, _, _, contextInfo) as e), _) when
780790
(match contextInfo with
@@ -812,12 +822,15 @@ type Exception with
812822
os.AppendString(SeeAlsoE().Format(stringOfRange m1))
813823

814824
| ErrorFromAddingTypeEquation(g, denv, ty1, ty2, e, _) ->
825+
let typeEquation2E =
826+
typeEquationMessage g ty2 ErrorFromAddingTypeEquation2E ErrorFromAddingTypeEquation2TupleE
827+
815828
let e =
816829
if not (typeEquiv g ty1 ty2) then
817830
let ty1, ty2, tpcs = NicePrint.minimalStringsOfTwoTypes denv ty1 ty2
818831

819832
if ty1 <> ty2 + tpcs then
820-
os.AppendString(ErrorFromAddingTypeEquation2E().Format ty1 ty2 tpcs)
833+
os.AppendString(typeEquation2E().Format ty1 ty2 tpcs)
821834

822835
e
823836

@@ -1009,11 +1022,13 @@ type Exception with
10091022
| Some name -> os.AppendString(FSComp.SR.notAFunctionButMaybeIndexerWithName2 name)
10101023
| _ -> os.AppendString(FSComp.SR.notAFunctionButMaybeIndexer2 ())
10111024

1012-
| NotAFunction(_, _, _, marg) ->
1025+
| NotAFunction(denv, ty, _, marg) ->
10131026
if marg.StartColumn = 0 then
10141027
os.AppendString(FSComp.SR.notAFunctionButMaybeDeclaration ())
1015-
else
1028+
elif isTyparTy denv.g ty then
10161029
os.AppendString(FSComp.SR.notAFunction ())
1030+
else
1031+
os.AppendString(FSComp.SR.notAFunctionWithType (NicePrint.prettyStringOfTy denv ty))
10171032

10181033
| TyconBadArgs(_, tcref, d, _) ->
10191034
let exp = tcref.TyparsNoRange.Length
@@ -1789,6 +1804,19 @@ type Exception with
17891804
if isComp then
17901805
os.AppendString(MatchIncomplete4E().Format)
17911806

1807+
| PatternMatchCompilation.MatchIncompleteForLoopHint(PatternMatchCompilation.MatchIncomplete(isComp, cexOpt, _)) ->
1808+
os.AppendString(MatchIncomplete1E().Format)
1809+
1810+
match cexOpt with
1811+
| None -> ()
1812+
| Some(cex, false) -> os.AppendString(MatchIncomplete2E().Format cex)
1813+
| Some(cex, true) -> os.AppendString(MatchIncomplete3E().Format cex)
1814+
1815+
os.AppendString(MatchIncompleteForLoopE().Format)
1816+
1817+
if isComp then
1818+
os.AppendString(MatchIncomplete4E().Format)
1819+
17921820
| PatternMatchCompilation.EnumMatchIncomplete(isComp, cexOpt, _) ->
17931821
os.AppendString(EnumMatchIncomplete1E().Format)
17941822

0 commit comments

Comments
 (0)