Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### Fixed

* Fixed: Inheriting from an undefined type now reports `FS0039` exactly once instead of three times. Phase 1F and Phase 2A of inherit-clause type-checking now skip re-resolving a syntactic clause whose Phase 1D resolution already failed with `UndefinedName`, eliminating both the duplicate diagnostic and the redundant work. ([Issue #16432](https://github.com/dotnet/fsharp/issues/16432), [PR #19862](https://github.com/dotnet/fsharp/pull/19862))
* Suppress hover/symbol resolution for wildcard `_` patterns inside `member _.…` bodies that incorrectly showed `val _: T` tooltip. ([PR #19760](https://github.com/dotnet/fsharp/pull/19760))
* Deduplicate format specifier locations in computation expressions so editor tooling no longer reports duplicate entries for the same `%` specifier. ([Issue #16419](https://github.com/dotnet/fsharp/issues/16419), [PR #19791](https://github.com/dotnet/fsharp/pull/19791))
* Reject non-function bindings for single-case and partial active pattern names with FS1209, matching the existing multi-case behavior. ([PR #19763](https://github.com/dotnet/fsharp/pull/19763))
Expand Down
4 changes: 4 additions & 0 deletions src/Compiler/Checking/CheckBasics.fs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,9 @@ type TcFileState =

argInfoCache: ConcurrentDictionary<string * range, ArgReprInfo>

/// Inherit clauses whose type already failed UndefinedName; skip re-resolution to avoid duplicate FS0039.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment duplicates the signature.

inheritResolutionFailed: ConcurrentDictionary<struct (Stamp * range), unit>

// forward call
TcPat: WarnOnUpperFlag -> TcFileState -> TcEnv -> PrelimValReprInfo option -> TcPatValFlags -> TcPatLinearEnv -> TType -> SynPat -> (TcPatPhase2Input -> Pattern) * TcPatLinearEnv

Expand Down Expand Up @@ -363,6 +366,7 @@ type TcFileState =
isInternalTestSpanStackReferring = isInternalTestSpanStackReferring
diagnosticOptions = diagnosticOptions
argInfoCache = ConcurrentDictionary()
inheritResolutionFailed = ConcurrentDictionary()
TcPat = tcPat
TcSimplePats = tcSimplePats
TcSequenceExpressionEntry = tcSequenceExpressionEntry
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/Checking/CheckBasics.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,9 @@ type TcFileState =
/// we're always dealing with the same instance and the updates don't get lost
argInfoCache: ConcurrentDictionary<string * range, ArgReprInfo>

/// Inherit clauses whose type already failed UndefinedName; skip re-resolution to avoid duplicate FS0039.
inheritResolutionFailed: ConcurrentDictionary<struct (Stamp * range), unit>

// forward call
TcPat:
WarnOnUpperFlag
Expand Down
44 changes: 36 additions & 8 deletions src/Compiler/Checking/CheckDeclarations.fs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ open FSharp.Compiler.TypeProviders

type cenv = TcFileState

let rec (|UndefinedNameError|_|) (e: exn) =
match e with
| UndefinedName _ -> Some ()
| WrappedError(inner, _)
| ErrorFromAddingTypeEquation(error = inner)
| ErrorFromAddingConstraint(error = inner)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely not a undefined name error.

| ErrorFromApplyingDefault(error = inner) -> (|UndefinedNameError|_|) inner
| _ -> None

//-------------------------------------------------------------------------
// Mutually recursive shapes
//-------------------------------------------------------------------------
Expand Down Expand Up @@ -1367,14 +1376,17 @@ module MutRecBindingChecking =
// Phase2B: typecheck the argument to an 'inherits' call and build the new object expr for the inherit-call
| Phase2AInherit (synBaseTy, arg, baseValOpt, m) ->
let inheritsExpr, tpenv =
try
let baseTy, tpenv = TcType cenv NoNewTypars CheckCxs ItemOccurrence.Use WarnOnIWSAM.Yes envInstance tpenv synBaseTy
let baseTy = baseTy |> convertToTypeWithMetadataIfPossible g
let mTcNew = unionRanges synBaseTy.Range arg.Range
TcNewExpr cenv envInstance tpenv baseTy (Some synBaseTy.Range) true arg mTcNew
with RecoverableException e ->
errorRecovery e m
if cenv.inheritResolutionFailed.ContainsKey(struct (tcref.Stamp, synBaseTy.Range)) then
mkUnit g m, tpenv
else
Comment on lines +1379 to +1381

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I'm following the idea. If there was an error previously then we skip the analysis and if there wasn't we still do it the second time? Why do we need to resolve the same identifiers more than once at all?

try
let baseTy, tpenv = TcType cenv NoNewTypars CheckCxs ItemOccurrence.Use WarnOnIWSAM.Yes envInstance tpenv synBaseTy
let baseTy = baseTy |> convertToTypeWithMetadataIfPossible g
let mTcNew = unionRanges synBaseTy.Range arg.Range
TcNewExpr cenv envInstance tpenv baseTy (Some synBaseTy.Range) true arg mTcNew
with RecoverableException e ->
errorRecovery e m
mkUnit g m, tpenv
let envInstance = match baseValOpt with Some baseVal -> AddLocalVal g cenv.tcSink scopem baseVal envInstance | None -> envInstance
let envNonRec = match baseValOpt with Some baseVal -> AddLocalVal g cenv.tcSink scopem baseVal envNonRec | None -> envNonRec
let innerState = (tpenv, envInstance, envStatic, envNonRec, generalizedRecBinds, preGeneralizationRecBinds, uncheckedRecBindsTable)
Expand Down Expand Up @@ -3317,7 +3329,23 @@ module EstablishTypeDefinitionCores =
let kind = InferTyconKind g (kind, attrs, slotsigs, fields, inSig, isConcrete, m)

let inherits = inherits |> List.map (fun (ty, m, _) -> (ty, m))
let inheritedTys = fst (List.mapFold (mapFoldFst (TcTypeAndRecover cenv NoNewTypars checkConstraints ItemOccurrence.UseInType WarnOnIWSAM.No envinner)) tpenv inherits)
let tryResolveInheritType tpenv (ty: SynType, m) =
let key = struct (tcref.Stamp, ty.Range)
if cenv.inheritResolutionFailed.ContainsKey key then
(g.obj_ty_ambivalent, m), tpenv
else
try
let resolved, tpenv = TcType cenv NoNewTypars checkConstraints ItemOccurrence.UseInType WarnOnIWSAM.No envinner tpenv ty
(resolved, m), tpenv
with
| RecoverableException (UndefinedNameError as e) ->
errorRecovery e ty.Range
cenv.inheritResolutionFailed.TryAdd(key, ()) |> ignore
(g.obj_ty_ambivalent, m), tpenv
| RecoverableException e ->
errorRecovery e ty.Range
(g.obj_ty_ambivalent, m), tpenv
let inheritedTys = inherits |> List.mapFold tryResolveInheritType tpenv |> fst
let implementedTys, inheritedTys =
match kind with
| SynTypeDefnKind.Interface ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,55 @@ module InheritsDeclarations =
|> ignoreWarnings
|> compile
|> shouldSucceed

[<Fact>]
let ``Inherit from undefined non-generic type reports single FS0039`` () =
FSharp "type MyClass() =\n inherit NonExistentBase()\n"
|> typecheck
|> shouldFail
|> withDiagnostics [
(Error 39, Line 2, Col 13, Line 2, Col 28, "The type 'NonExistentBase' is not defined.")
]

[<Fact>]
let ``Inherit from undefined generic type reports single FS0039`` () =
FSharp "type MyClass() =\n inherit MissingGeneric<int>()\n"
|> typecheck
|> shouldFail
|> withDiagnostics [
(Error 39, Line 2, Col 13, Line 2, Col 27, "The type 'MissingGeneric' is not defined.")
]

[<Fact>]
let ``Interface inheriting undefined interface reports single FS0039`` () =
FSharp "type IMyInterface =\n inherit INonExistent\n"
|> typecheck
|> shouldFail
|> withDiagnostics [
(Error 39, Line 2, Col 13, Line 2, Col 25, "The type 'INonExistent' is not defined.")
(Error 887, Line 2, Col 5, Line 2, Col 25, "The type 'obj' is not an interface type")
]

[<Fact>]
let ``Undefined inherit and undefined value report independently`` () =
FSharp """
type MyClass() =
inherit NonExistentBase()
member _.X = undefinedValue
"""
|> typecheck
|> shouldFail
|> withDiagnostics [
(Error 39, Line 3, Col 13, Line 3, Col 28, "The type 'NonExistentBase' is not defined.")
(Error 39, Line 4, Col 18, Line 4, Col 32, "The value or constructor 'undefinedValue' is not defined.")
]

[<Fact>]
let ``Valid inherit produces no diagnostics`` () =
FSharp """
open System
type MyClass() =
inherit Exception("test")
"""
|> typecheck
|> shouldSucceed
Loading