diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 965957dbc0c..c36cddf803d 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -75,6 +75,7 @@ ([PR #19724](https://github.com/dotnet/fsharp/pull/19724)) * Emit debug points at a stack-empty position ([PR #19877](https://github.com/dotnet/fsharp/pull/19877)) * Fix spurious XmlDoc warnings (unknown parameter / no documentation for parameter) under `--warnon:3390` when a get/set property documents the full parameter set across both accessors. ([Issue #13684](https://github.com/dotnet/fsharp/issues/13684), [PR #19884](https://github.com/dotnet/fsharp/pull/19884)) +* Fix Go-to-Definition for provided constructors that lack `TypeProviderDefinitionLocationAttribute`. Navigation now falls back to the declaring type instead of silently failing. ([Issue #5538](https://github.com/dotnet/fsharp/issues/5538), [PR #19917](https://github.com/dotnet/fsharp/pull/19917)) ### Added diff --git a/src/Compiler/Symbols/SymbolHelpers.fs b/src/Compiler/Symbols/SymbolHelpers.fs index 742e7640293..442fb873fc6 100644 --- a/src/Compiler/Symbols/SymbolHelpers.fs +++ b/src/Compiler/Symbols/SymbolHelpers.fs @@ -55,14 +55,18 @@ module internal SymbolHelpers = let rangeOfPropInfo preferFlag (pinfo: PropInfo) = match pinfo with #if !NO_TYPEPROVIDERS - | ProvidedProp(_, pi, _) -> Construct.ComputeDefinitionLocationOfProvidedItem pi + | ProvidedProp(_, pi, _) -> + Construct.ComputeDefinitionLocationOfProvidedItem pi + |> Option.orElseWith (fun () -> Some(rangeOfEntityRef preferFlag pinfo.DeclaringTyconRef)) #endif | _ -> pinfo.ArbitraryValRef |> Option.map (rangeOfValRef preferFlag) let rangeOfMethInfo (g: TcGlobals) preferFlag (minfo: MethInfo) = match minfo with #if !NO_TYPEPROVIDERS - | ProvidedMeth(_, mi, _, _) -> Construct.ComputeDefinitionLocationOfProvidedItem mi + | ProvidedMeth(_, mi, _, _) -> + Construct.ComputeDefinitionLocationOfProvidedItem mi + |> Option.orElseWith (fun () -> Some(rangeOfEntityRef preferFlag minfo.DeclaringTyconRef)) #endif | DefaultStructCtor(_, AppTy g (tcref, _)) -> Some(rangeOfEntityRef preferFlag tcref) | _ -> minfo.ArbitraryValRef |> Option.map (rangeOfValRef preferFlag) @@ -70,7 +74,9 @@ module internal SymbolHelpers = let rangeOfEventInfo preferFlag (einfo: EventInfo) = match einfo with #if !NO_TYPEPROVIDERS - | ProvidedEvent (_, ei, _) -> Construct.ComputeDefinitionLocationOfProvidedItem ei + | ProvidedEvent(_, ei, _) -> + Construct.ComputeDefinitionLocationOfProvidedItem ei + |> Option.orElseWith (fun () -> Some(rangeOfEntityRef preferFlag einfo.DeclaringTyconRef)) #endif | _ -> einfo.ArbitraryValRef |> Option.map (rangeOfValRef preferFlag) diff --git a/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs b/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs index 6b4f6d946f6..2b1ef8ebe68 100644 --- a/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs @@ -3788,6 +3788,84 @@ let ``Test Project25 symbol uses of type-provided members`` () = [| ("file1", ((5, 8), (5, 21))) // line 5: T().DoNothing ("file1", ((10, 8), (10, 26))) |] // line 10: MyType().DoNothing +[] +let ``GetDeclarationLocation on a provided-ctor without DefinitionLocationAttribute returns DeclFound (regression #5538)`` () = + let wholeProjectResults = + Project25.checker.ParseAndCheckProject(Project25.options.Value) + |> Async.RunImmediate + + wholeProjectResults.Diagnostics.Length |> shouldEqual 0 + + let parseResults, checkAnswer = + Project25.checker.ParseAndCheckFileInProject( + Project25.fileName1, + 0, + SourceText.ofString (FileSystem.OpenFileForReadShim(Project25.fileName1).ReadAllText()), + Project25.options.Value) + |> Async.RunImmediate + + let checkResults = + match checkAnswer with + | FSharpCheckFileAnswer.Succeeded r -> r + | _ -> failwith "type-check did not finish" + + ignore parseResults + + // line 5: `let _ = T().DoNothing()` + // col-index 8 is `T`, end of identifier is col 9. + // `T` at this position resolves to Item.CtorGroup(ProvidedMeth :: _), + // which is the broken case in issue #5538. + let declLocation = + checkResults.GetDeclarationLocation( + line = 5, + colAtEndOfNames = 9, + lineText = "let _ = T().DoNothing()", + names = [ "T" ]) + + match declLocation with + | FindDeclResult.DeclFound _ -> () + | FindDeclResult.DeclNotFound reason -> + failwithf "expected DeclFound for provided-ctor `T()`, got DeclNotFound %A" reason + | FindDeclResult.ExternalDecl _ -> + failwith "expected DeclFound for provided-ctor `T()`, got ExternalDecl" + +[] +let ``GetDeclarationLocation on a provided-ctor invoked through the original provided name returns DeclFound (regression #5538)`` () = + let wholeProjectResults = + Project25.checker.ParseAndCheckProject(Project25.options.Value) + |> Async.RunImmediate + + wholeProjectResults.Diagnostics.Length |> shouldEqual 0 + + let _, checkAnswer = + Project25.checker.ParseAndCheckFileInProject( + Project25.fileName1, + 0, + SourceText.ofString (FileSystem.OpenFileForReadShim(Project25.fileName1).ReadAllText()), + Project25.options.Value) + |> Async.RunImmediate + + let checkResults = + match checkAnswer with + | FSharpCheckFileAnswer.Succeeded r -> r + | _ -> failwith "type-check did not finish" + + // line 10: `let _ = MyType().DoNothing()` + // col-index 8 is `M`, end of identifier `MyType` is col 14. + let declLocation = + checkResults.GetDeclarationLocation( + line = 10, + colAtEndOfNames = 14, + lineText = "let _ = MyType().DoNothing()", + names = [ "MyType" ]) + + match declLocation with + | FindDeclResult.DeclFound _ -> () + | FindDeclResult.DeclNotFound reason -> + failwithf "expected DeclFound for provided-ctor `MyType()`, got DeclNotFound %A" reason + | FindDeclResult.ExternalDecl _ -> + failwith "expected DeclFound for provided-ctor `MyType()`, got ExternalDecl" + [] let ``Test Project25 symbol uses of type-provided types`` () = let wholeProjectResults = Project25.checker.ParseAndCheckProject(Project25.options.Value) |> Async.RunImmediate