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/10.0.300.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
### Added
* FSharpType: add ImportILType ([PR #19300](https://github.com/dotnet/fsharp/pull/19300))
* Type checker: recover on argument/overload checking ([PR #19314](https://github.com/dotnet/fsharp/pull/19314))
* Introduced a separate `NotifyRelatedSymbolUse` sink and `[<Flags>] RelatedSymbolUseKind` enum for related symbol lookups (union case testers, copy-and-update record types). `GetUsesOfSymbolInFile` and `GetSemanticClassification` accept an optional `relatedSymbolKinds` parameter to opt in. ([PR #19361](https://github.com/dotnet/fsharp/pull/19361))

### Changed

Expand Down
11 changes: 6 additions & 5 deletions src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ open FSharp.Compiler.DiagnosticsLogger
open FSharp.Compiler.Features
open FSharp.Compiler.Infos
open FSharp.Compiler.InfoReader
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.MethodCalls
open FSharp.Compiler.MethodOverrides
open FSharp.Compiler.NameResolution
Expand Down Expand Up @@ -1847,6 +1848,8 @@ let MakeAndPublishSimpleValsForMergedScope (cenv: cenv) env m (names: NameMap<_>

member _.NotifyFormatSpecifierLocation(_, _) = ()

member _.NotifyRelatedSymbolUse(_, _, _) = ()

member _.NotifyOpenDeclaration _ = ()

member _.CurrentSourceText = None
Expand Down Expand Up @@ -7874,14 +7877,12 @@ and TcRecdExpr cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, m
let gtyp = mkWoNullAppTy tcref tinst
UnifyTypes cenv env mWholeExpr overallTy gtyp

// (#15290) For copy-and-update expressions, register the record type as a reference
// (#15290) For copy-and-update expressions, register the record type as a related symbol
// so that "Find All References" on the record type includes copy-and-update usages.
// Use a zero-width range at the start of the expression to avoid affecting semantic
// classification (coloring) of field names and other tokens within the expression.
// Reported via CallRelatedSymbolSink to avoid affecting colorization or symbol info.
if hasOrigExpr then
let item = Item.Types(tcref.DisplayName, [gtyp])
let pointRange = Range.mkRange mWholeExpr.FileName mWholeExpr.Start mWholeExpr.Start
CallNameResolutionSink cenv.tcSink (pointRange, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, env.eAccessRights)
CallRelatedSymbolSink cenv.tcSink (mWholeExpr, item, RelatedSymbolUseKind.CopyAndUpdateRecord)

[ for n, v in fldsList do
match v with
Expand Down
64 changes: 46 additions & 18 deletions src/Compiler/Checking/NameResolution.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ open FSharp.Compiler.AbstractIL.Diagnostics
open FSharp.Compiler.AbstractIL.IL
open FSharp.Compiler.AccessibilityLogic
open FSharp.Compiler.AttributeChecking
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.DiagnosticsLogger
open FSharp.Compiler.CompilerGlobalState
open FSharp.Compiler.InfoReader
Expand Down Expand Up @@ -1793,6 +1794,8 @@ type ITypecheckResultsSink =

abstract NotifyFormatSpecifierLocation: range * int -> unit

abstract NotifyRelatedSymbolUse: range * Item * RelatedSymbolUseKind -> unit

abstract NotifyOpenDeclaration: OpenDeclaration -> unit

abstract CurrentSourceText: ISourceText option
Expand Down Expand Up @@ -2046,9 +2049,10 @@ type TcResolutions
(capturedEnvs: ResizeArray<range * NameResolutionEnv * AccessorDomain>,
capturedExprTypes: ResizeArray<TType * NameResolutionEnv * AccessorDomain * range>,
capturedNameResolutions: ResizeArray<CapturedNameResolution>,
capturedMethodGroupResolutions: ResizeArray<CapturedNameResolution>) =
capturedMethodGroupResolutions: ResizeArray<CapturedNameResolution>,
capturedRelatedSymbolUses: ResizeArray<range * Item * RelatedSymbolUseKind>) =

static let empty = TcResolutions(ResizeArray 0, ResizeArray 0, ResizeArray 0, ResizeArray 0)
static let empty = TcResolutions(ResizeArray 0, ResizeArray 0, ResizeArray 0, ResizeArray 0, ResizeArray 0)

member _.CapturedEnvs = capturedEnvs

Expand All @@ -2058,6 +2062,8 @@ type TcResolutions

member _.CapturedMethodGroupResolutions = capturedMethodGroupResolutions

member _.CapturedRelatedSymbolUses = capturedRelatedSymbolUses

static member Empty = empty

[<Struct>]
Expand All @@ -2072,37 +2078,53 @@ type TcSymbolUseData =
/// This is a memory-critical data structure - allocations of this data structure and its immediate contents
/// is one of the highest memory long-lived data structures in typical uses of IDEs. Not many of these objects
/// are allocated (one per file), but they are large because the allUsesOfAllSymbols array is large.
type TcSymbolUses(g, capturedNameResolutions: ResizeArray<CapturedNameResolution>, formatSpecifierLocations: (range * int)[]) =
type TcSymbolUses(g, capturedNameResolutions: ResizeArray<CapturedNameResolution>, capturedRelatedSymbolUses: ResizeArray<range * Item * RelatedSymbolUseKind>, formatSpecifierLocations: (range * int)[]) =

let toSymbolUseData (cnr: CapturedNameResolution) =
{ ItemWithInst = cnr.ItemWithInst; ItemOccurrence = cnr.ItemOccurrence; DisplayEnv = cnr.DisplayEnv; Range = cnr.Range }

// Make sure we only capture the information we really need to report symbol uses
let allUsesOfSymbols =
capturedNameResolutions
|> ResizeArray.mapToSmallArrayChunks (fun cnr -> { ItemWithInst=cnr.ItemWithInst; ItemOccurrence=cnr.ItemOccurrence; DisplayEnv=cnr.DisplayEnv; Range=cnr.Range })
|> ResizeArray.mapToSmallArrayChunks toSymbolUseData

let capturedNameResolutions = ()
do capturedNameResolutions // don't capture this!

member _.GetUsesOfSymbol item =
// This member returns what is potentially a very large array, which may approach the size constraints of the Large Object Heap.
// This is unlikely in practice, though, because we filter down the set of all symbol uses to those specifically for the given `item`.
// Consequently we have a much lesser chance of ending up with an array large enough to be promoted to the LOH.
let relatedSymbolUses =
capturedRelatedSymbolUses
|> ResizeArray.mapToSmallArrayChunks (fun (m, item, kind) ->
struct ({ ItemWithInst = { Item = item; TyparInstantiation = emptyTyparInst }; ItemOccurrence = ItemOccurrence.Use; DisplayEnv = DisplayEnv.Empty g; Range = m }, kind))

let capturedRelatedSymbolUses = ()
do capturedRelatedSymbolUses // don't capture this!

member _.GetUsesOfSymbol(item, ?relatedSymbolKinds: RelatedSymbolUseKind) =
let kinds = defaultArg relatedSymbolKinds RelatedSymbolUseKind.None
[| for symbolUseChunk in allUsesOfSymbols do
for symbolUse in symbolUseChunk do
if protectAssemblyExploration false (fun () -> ItemsAreEffectivelyEqual g item symbolUse.ItemWithInst.Item) then
yield symbolUse |]
yield symbolUse
if kinds <> RelatedSymbolUseKind.None then
for chunk in relatedSymbolUses do
for struct (symbolUse, kind) in chunk do
if kinds.HasFlag kind then
if protectAssemblyExploration false (fun () -> ItemsAreEffectivelyEqual g item symbolUse.ItemWithInst.Item) then
yield symbolUse |]

member _.AllUsesOfSymbols = allUsesOfSymbols

member _.GetFormatSpecifierLocationsAndArity() = formatSpecifierLocations

static member Empty = TcSymbolUses(Unchecked.defaultof<_>, ResizeArray(), Array.empty)
static member Empty = TcSymbolUses(Unchecked.defaultof<_>, ResizeArray(), ResizeArray(), Array.empty)

/// An accumulator for the results being emitted into the tcSink.
type TcResultsSinkImpl(tcGlobals, ?sourceText: ISourceText) =
let capturedEnvs = ResizeArray<_>()
let capturedExprTypings = ResizeArray<_>()
let capturedNameResolutions = ResizeArray<CapturedNameResolution>()
let capturedMethodGroupResolutions = ResizeArray<CapturedNameResolution>()
let capturedRelatedSymbolUses = ResizeArray<range * Item * RelatedSymbolUseKind>()
let capturedOpenDeclarations = ResizeArray<OpenDeclaration>()
let capturedFormatSpecifierLocations = ResizeArray<_>()

Expand Down Expand Up @@ -2168,10 +2190,10 @@ type TcResultsSinkImpl(tcGlobals, ?sourceText: ISourceText) =
LineStartPositions = positions })

member _.GetResolutions() =
TcResolutions(capturedEnvs, capturedExprTypings, capturedNameResolutions, capturedMethodGroupResolutions)
TcResolutions(capturedEnvs, capturedExprTypings, capturedNameResolutions, capturedMethodGroupResolutions, capturedRelatedSymbolUses)

member _.GetSymbolUses() =
TcSymbolUses(tcGlobals, capturedNameResolutions, capturedFormatSpecifierLocations.ToArray())
TcSymbolUses(tcGlobals, capturedNameResolutions, capturedRelatedSymbolUses, capturedFormatSpecifierLocations.ToArray())

member _.GetOpenDeclarations() =
capturedOpenDeclarations |> Seq.distinctBy (fun x -> x.Range, x.AppliedScope, x.IsOwnNamespace) |> Seq.toArray
Expand Down Expand Up @@ -2208,6 +2230,10 @@ type TcResultsSinkImpl(tcGlobals, ?sourceText: ISourceText) =
member sink.NotifyFormatSpecifierLocation(m, numArgs) =
capturedFormatSpecifierLocations.Add((m, numArgs))

member sink.NotifyRelatedSymbolUse(m, item, kind) =
if allowedRange m then
capturedRelatedSymbolUses.Add((m, item, kind))

member sink.NotifyOpenDeclaration openDeclaration =
capturedOpenDeclarations.Add openDeclaration

Expand Down Expand Up @@ -2245,10 +2271,7 @@ let CallEnvSink (sink: TcResultsSink) (scopem, nenv, ad) =
let RegisterUnionCaseTesterForProperty
(sink: TcResultsSink)
(identRange: range)
(nenv: NameResolutionEnv)
(pinfos: PropInfo list)
(occurrenceType: ItemOccurrence)
(ad: AccessorDomain)
=
match sink.CurrentSink, pinfos with
| Some currentSink, (pinfo :: _) when pinfo.IsUnionCaseTester ->
Expand All @@ -2263,7 +2286,7 @@ let RegisterUnionCaseTesterForProperty
let ucref = tcref.MakeNestedUnionCaseRef ucase
let ucinfo = UnionCaseInfo([], ucref)
let ucItem = Item.UnionCase(ucinfo, false)
currentSink.NotifyNameResolution(identRange.End, ucItem, emptyTyparInst, occurrenceType, nenv, ad, identRange, true)
currentSink.NotifyRelatedSymbolUse(identRange, ucItem, RelatedSymbolUseKind.UnionCaseTester)
| None -> ()
| _ -> ()

Expand All @@ -2286,6 +2309,11 @@ let CallNameResolutionSinkReplacing (sink: TcResultsSink) (m: range, nenv, item,
| Some currentSink ->
currentSink.NotifyNameResolution(m.End, item, tpinst, occurrenceType, nenv, ad, m, true)

let CallRelatedSymbolSink (sink: TcResultsSink) (m: range, item: Item, kind: RelatedSymbolUseKind) =
match sink.CurrentSink with
| None -> ()
| Some currentSink -> currentSink.NotifyRelatedSymbolUse(m, item, kind)

/// Report a specific expression typing at a source range
let CallExprHasTypeSink (sink: TcResultsSink) (m: range, nenv, ty, ad) =
match sink.CurrentSink with
Expand Down Expand Up @@ -4192,7 +4220,7 @@ let ResolveLongIdentAsExprAndComputeRange (sink: TcResultsSink) (ncenv: NameReso
match refinedItem with
| Item.Property(_, pinfos, _) ->
let propIdentRange = if rest.IsEmpty then (List.last lid).idRange else itemRange
RegisterUnionCaseTesterForProperty sink propIdentRange nenv pinfos occurrence ad
RegisterUnionCaseTesterForProperty sink propIdentRange pinfos
| _ -> ()

let callSinkWithSpecificOverload (minfo: MethInfo, pinfoOpt: PropInfo option, tpinst) =
Expand Down Expand Up @@ -4268,7 +4296,7 @@ let ResolveExprDotLongIdentAndComputeRange (sink: TcResultsSink) (ncenv: NameRes
match refinedItem with
| Item.Property(_, pinfos, _) ->
let propIdentRange = if rest.IsEmpty then (List.last lid).idRange else itemRange
RegisterUnionCaseTesterForProperty sink propIdentRange nenv pinfos ItemOccurrence.Use ad
RegisterUnionCaseTesterForProperty sink propIdentRange pinfos
| _ -> ()

let callSinkWithSpecificOverload (minfo: MethInfo, pinfoOpt: PropInfo option, tpinst) =
Expand Down
16 changes: 13 additions & 3 deletions src/Compiler/Checking/NameResolution.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module internal FSharp.Compiler.NameResolution

open Internal.Utilities.Library
open FSharp.Compiler.AccessibilityLogic
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Infos
open FSharp.Compiler.Import
open FSharp.Compiler.InfoReader
Expand Down Expand Up @@ -437,6 +438,9 @@ type internal TcResolutions =
/// See TypeCheckInfo.GetCapturedNameResolutions for example.
member CapturedMethodGroupResolutions: ResizeArray<CapturedNameResolution>

/// Related symbol uses reported via NotifyRelatedSymbolUse
member CapturedRelatedSymbolUses: ResizeArray<range * Item * RelatedSymbolUseKind>

/// Represents the empty set of resolutions
static member Empty: TcResolutions

Expand All @@ -452,7 +456,7 @@ type TcSymbolUseData =
type internal TcSymbolUses =

/// Get all the uses of a particular item within the file
member GetUsesOfSymbol: Item -> TcSymbolUseData[]
member GetUsesOfSymbol: item: Item * ?relatedSymbolKinds: RelatedSymbolUseKind -> TcSymbolUseData[]

/// All the uses of all items within the file
member AllUsesOfSymbols: TcSymbolUseData[][]
Expand Down Expand Up @@ -494,6 +498,10 @@ type ITypecheckResultsSink =
/// Record that a printf format specifier occurred at a specific location in the source
abstract NotifyFormatSpecifierLocation: range * int -> unit

/// Record that a symbol is implicitly referenced at a source range.
/// Unlike NotifyNameResolution, this does not affect colorization or symbol info.
abstract NotifyRelatedSymbolUse: range * Item * RelatedSymbolUseKind -> unit

/// Record that an open declaration occurred in a given scope range
abstract NotifyOpenDeclaration: OpenDeclaration -> unit

Expand Down Expand Up @@ -629,8 +637,10 @@ val internal CallNameResolutionSinkReplacing:
TcResultsSink -> range * NameResolutionEnv * Item * TyparInstantiation * ItemOccurrence * AccessorDomain -> unit

/// #16621
val internal RegisterUnionCaseTesterForProperty:
TcResultsSink -> identRange: range -> NameResolutionEnv -> PropInfo list -> ItemOccurrence -> AccessorDomain -> unit
val internal RegisterUnionCaseTesterForProperty: TcResultsSink -> identRange: range -> PropInfo list -> unit

/// Report a related symbol use at a source range (does not affect colorization or symbol info)
val internal CallRelatedSymbolSink: TcResultsSink -> range * Item * RelatedSymbolUseKind -> unit

/// Report a specific name resolution at a source range
val internal CallExprHasTypeSink: TcResultsSink -> range * NameResolutionEnv * TType * AccessorDomain -> unit
Expand Down
17 changes: 17 additions & 0 deletions src/Compiler/Checking/RelatedSymbolUse.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace FSharp.Compiler.CodeAnalysis

/// Classifies symbols that are related to a name resolution but are not the direct resolution result.
/// Used to report additional symbols (e.g., union case testers, copy-and-update record types)
/// via a separate sink so they don't corrupt colorization or symbol info.
[<System.Flags>]
type RelatedSymbolUseKind =
/// No related symbols
| None = 0
/// Union case via tester property (e.g., .IsCaseA → CaseA)
| UnionCaseTester = 1
/// Record type via copy-and-update expression (e.g., { r with ... } → RecordType)
| CopyAndUpdateRecord = 2
/// All related symbol kinds
| All = 0x7FFFFFFF
1 change: 1 addition & 0 deletions src/Compiler/FSharp.Compiler.Service.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@
<Compile Include="Checking\NicePrint.fs" />
<Compile Include="Checking\AugmentWithHashCompare.fsi" />
<Compile Include="Checking\AugmentWithHashCompare.fs" />
<Compile Include="Checking\RelatedSymbolUse.fs" />
<Compile Include="Checking\NameResolution.fsi" />
<Compile Include="Checking\NameResolution.fs" />
<Compile Include="Checking\SignatureConformance.fsi" />
Expand Down
12 changes: 6 additions & 6 deletions src/Compiler/Service/FSharpCheckerResults.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2784,8 +2784,8 @@ type internal TypeCheckInfo
member _.GetFormatSpecifierLocationsAndArity() =
sSymbolUses.GetFormatSpecifierLocationsAndArity()

member _.GetSemanticClassification(range: range option) : SemanticClassificationItem[] =
sResolutions.GetSemanticClassification(g, amap, sSymbolUses.GetFormatSpecifierLocationsAndArity(), range)
member _.GetSemanticClassification(range: range option, ?relatedSymbolKinds: RelatedSymbolUseKind) : SemanticClassificationItem[] =
sResolutions.GetSemanticClassification(g, amap, sSymbolUses.GetFormatSpecifierLocationsAndArity(), range, ?relatedSymbolKinds = relatedSymbolKinds)

/// The resolutions in the file
member _.ScopeResolutions = sResolutions
Expand Down Expand Up @@ -3506,10 +3506,10 @@ type FSharpCheckFileResults
| None -> [||]
| Some(scope, _builderOpt) -> scope.GetFormatSpecifierLocationsAndArity()

member _.GetSemanticClassification(range: range option) =
member _.GetSemanticClassification(range: range option, ?relatedSymbolKinds: RelatedSymbolUseKind) =
match details with
| None -> [||]
| Some(scope, _builderOpt) -> scope.GetSemanticClassification(range)
| Some(scope, _builderOpt) -> scope.GetSemanticClassification(range, ?relatedSymbolKinds = relatedSymbolKinds)

member _.PartialAssemblySignature =
match details with
Expand Down Expand Up @@ -3554,13 +3554,13 @@ type FSharpCheckFileResults
FSharpSymbolUse(symbolUse.DisplayEnv, symbol, inst, symbolUse.ItemOccurrence, symbolUse.Range)
}

member _.GetUsesOfSymbolInFile(symbol: FSharpSymbol, ?cancellationToken: CancellationToken) =
member _.GetUsesOfSymbolInFile(symbol: FSharpSymbol, ?relatedSymbolKinds: RelatedSymbolUseKind, ?cancellationToken: CancellationToken) =
match details with
| None -> [||]
| Some(scope, _builderOpt) ->
[|
for symbolUse in
scope.ScopeSymbolUses.GetUsesOfSymbol(symbol.Item)
scope.ScopeSymbolUses.GetUsesOfSymbol(symbol.Item, ?relatedSymbolKinds = relatedSymbolKinds)
|> Seq.distinctBy (fun symbolUse -> symbolUse.ItemOccurrence, symbolUse.Range) do
cancellationToken |> Option.iter (fun ct -> ct.ThrowIfCancellationRequested())

Expand Down
6 changes: 4 additions & 2 deletions src/Compiler/Service/FSharpCheckerResults.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ type public FSharpCheckFileResults =
line: int * colAtEndOfNames: int * lineText: string * names: string list -> FSharpSymbolUse list

/// <summary>Get any extra colorization info that is available after the typecheck</summary>
member GetSemanticClassification: range option -> SemanticClassificationItem[]
member GetSemanticClassification: range option * ?relatedSymbolKinds: RelatedSymbolUseKind -> SemanticClassificationItem[]

/// <summary>Get the locations of format specifiers</summary>
[<Obsolete("This member has been replaced by GetFormatSpecifierLocationsAndArity, which returns both range and arity of specifiers")>]
Expand All @@ -434,7 +434,9 @@ type public FSharpCheckFileResults =
member GetAllUsesOfAllSymbolsInFile: ?cancellationToken: CancellationToken -> seq<FSharpSymbolUse>

/// Get the textual usages that resolved to the given symbol throughout the file
member GetUsesOfSymbolInFile: symbol: FSharpSymbol * ?cancellationToken: CancellationToken -> FSharpSymbolUse[]
member GetUsesOfSymbolInFile:
symbol: FSharpSymbol * ?relatedSymbolKinds: RelatedSymbolUseKind * ?cancellationToken: CancellationToken ->
FSharpSymbolUse[]

member internal GetVisibleNamespacesAndModulesAtPoint: pos -> ModuleOrNamespaceRef[]

Expand Down
Loading
Loading