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,6 +1,7 @@
### Fixed

* Fix NRE when calling virtual Object methods on value types through inline SRTP functions. ([Issue #8098](https://github.com/dotnet/fsharp/issues/8098), [PR #19511](https://github.com/dotnet/fsharp/pull/19511))
* Narrow 'No overloads match for method' error range to method name only instead of covering the entire expression. Also narrow FSharpSymbolUse ranges reported through the name-resolution sink (used by Find Usages, symbol highlight, and semantic classification) so they report on the terminal identifier of a dotted long identifier instead of the whole object-expression path. ([Issue #14284](https://github.com/dotnet/fsharp/issues/14284), [Issue #3920](https://github.com/dotnet/fsharp/issues/3920), [PR #19505](https://github.com/dotnet/fsharp/pull/19505))
* Fix DU case names matching IWSAM member names no longer cause duplicate property entries. (Issue [#14321](https://github.com/dotnet/fsharp/issues/14321), [PR #19341](https://github.com/dotnet/fsharp/pull/19341))
* Fix DefaultAugmentation(false) duplicate entry in method table. (Issue [#16565](https://github.com/dotnet/fsharp/issues/16565), [PR #19341](https://github.com/dotnet/fsharp/pull/19341))
* Fix abstract event accessors now have SpecialName flag. (Issue [#5834](https://github.com/dotnet/fsharp/issues/5834), [PR #19341](https://github.com/dotnet/fsharp/pull/19341))
Expand Down
36 changes: 30 additions & 6 deletions src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6642,7 +6642,7 @@ and TcTyparExprThen (cenv: cenv) overallTy env tpenv synTypar m delayed =
| [] -> delayed2
| _ -> DelayedDotLookup (rest, m2) :: delayed2
CallNameResolutionSink cenv.tcSink (ident.idRange, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, env.AccessRights)
TcItemThen cenv overallTy env tpenv ([], item, mExprAndLongId, [], AfterResolution.DoNothing) (Some ty) delayed3
TcItemThen cenv overallTy env tpenv ([], item, mExprAndLongId, mExprAndLongId, [], AfterResolution.DoNothing) (Some ty) delayed3
//TcLookupItemThen cenv overallTy env tpenv mObjExpr objExpr objExprTy delayed item mItem rest afterResolution
| _ ->
let (SynTypar(_, q, _)) = synTypar
Expand Down Expand Up @@ -8631,7 +8631,7 @@ and TcNameOfExpr (cenv: cenv) env tpenv (synArg: SynExpr) =
let nameResolutionResult = ResolveLongIdentAsExprAndComputeRange cenv.tcSink cenv.nameResolver (rangeOfLid longId) ad env.eNameResEnv typeNameResInfo longId None
let resolvesAsExpr =
match nameResolutionResult with
| Result (_, item, _, _, _ as res)
| Result (_, item, _, _, _, _ as res)
when
(match item with
| Item.DelegateCtor _
Expand Down Expand Up @@ -8880,7 +8880,7 @@ and TcLongIdentThen (cenv: cenv) (overallTy: OverallTy) env tpenv (SynLongIdent(
//------------------------------------------------------------------------- *)

// mItem is the textual range covered by the long identifiers that make up the item
and TcItemThen (cenv: cenv) (overallTy: OverallTy) env tpenv (tinstEnclosing, item, mItem, rest, afterResolution) staticTyOpt delayed =
and TcItemThen (cenv: cenv) (overallTy: OverallTy) env tpenv (tinstEnclosing, item, mItem, _mItemIdent, rest, afterResolution) staticTyOpt delayed =
let delayed = delayRest rest mItem delayed
match item with
// x where x is a union case or active pattern result tag.
Expand Down Expand Up @@ -9120,8 +9120,8 @@ and TcTypeItemThen (cenv: cenv) overallTy env nm ty tpenv mItem tinstEnclosing d
let item = Item.Types(nm, [ty])
CallNameResolutionSink cenv.tcSink (mExprAndTypeArgs, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, env.eAccessRights)
let typeNameResInfo = GetLongIdentTypeNameInfo otherDelayed
let item, mItem, rest, afterResolution = ResolveExprDotLongIdentAndComputeRange cenv.tcSink cenv.nameResolver (unionRanges mExprAndTypeArgs mLongId) ad env.eNameResEnv ty longId typeNameResInfo IgnoreOverrides true None
TcItemThen cenv overallTy env tpenv ((argsOfAppTy g ty), item, mItem, rest, afterResolution) None otherDelayed
let item, mItem, mItemIdent, rest, afterResolution = ResolveExprDotLongIdentAndComputeRange cenv.tcSink cenv.nameResolver (unionRanges mExprAndTypeArgs mLongId) ad env.eNameResEnv ty longId typeNameResInfo IgnoreOverrides true None
TcItemThen cenv overallTy env tpenv ((argsOfAppTy g ty), item, mItem, mItemIdent, rest, afterResolution) None otherDelayed

| DelayedTypeApp(tyargs, _mTypeArgs, mExprAndTypeArgs) :: _delayed' ->
// A case where we have an incomplete name e.g. 'Foo<int>.' - we still want to report it to VS!
Expand Down Expand Up @@ -9720,7 +9720,7 @@ and TcLookupThen cenv overallTy env tpenv mObjExpr objExpr objExprTy longId dela
CanonicalizePartialInferenceProblem cenv.css env.DisplayEnv mExprAndLongId (freeInTypeLeftToRight g false objExprTy)

let maybeAppliedArgExpr = DelayedItem.maybeAppliedArgForPreferExtensionOverProperty delayed
let item, mItem, rest, afterResolution = ResolveExprDotLongIdentAndComputeRange cenv.tcSink cenv.nameResolver mExprAndLongId ad env.NameEnv objExprTy longId TypeNameResolutionInfo.Default findFlag false maybeAppliedArgExpr
let item, mItem, _mItemIdent, rest, afterResolution = ResolveExprDotLongIdentAndComputeRange cenv.tcSink cenv.nameResolver mExprAndLongId ad env.NameEnv objExprTy longId TypeNameResolutionInfo.Default findFlag false maybeAppliedArgExpr
TcLookupItemThen cenv overallTy env tpenv mObjExpr objExpr objExprTy delayed item mItem rest afterResolution

and TcLookupItemThen cenv overallTy env tpenv mObjExpr objExpr objExprTy delayed item mItem rest afterResolution =
Expand Down Expand Up @@ -10462,6 +10462,30 @@ and TcMethodApplication

let result, errors = ResolveOverloadingForCall denv cenv.css mMethExpr methodName callerArgs ad postArgumentTypeCheckingCalledMethGroup true returnTy

// Narrow the error range for unresolved overloading from the whole expression (mMethExpr)
// to just the method name. For instance calls like T.Instance.Method(""), mItem covers
// the entire "T.Instance.Method" range, so we compute the method-name-only range from
// the end of mItem and the method name length. See https://github.com/dotnet/fsharp/issues/14284.
let errors =
match errors with
| ErrorResult(warns, UnresolvedOverloading(denvErr, callerArgsErr, failure, _mWide)) ->
let mMethodName =
let itemWidth = mItem.EndColumn - mItem.StartColumn
// Only narrow when the range is single-line and the method name fits within it.
// Generic constructors may have internal names longer than the source text
// (e.g., "ImmutableStack`1" vs source "ImmutableStack").
if
mItem.StartLine = mItem.EndLine
&& methodName.Length < itemWidth
&& not (DoesIdentifierNeedBackticks methodName)
then
let startPos = mkPos mItem.EndLine (mItem.EndColumn - methodName.Length)
withStart startPos mItem
else
mItem
ErrorResult(warns, UnresolvedOverloading(denvErr, callerArgsErr, failure, mMethodName))
| other -> other

match afterResolution, result with
| AfterResolution.DoNothing, _ -> ()

Expand Down
72 changes: 49 additions & 23 deletions src/Compiler/Checking/NameResolution.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4160,14 +4160,36 @@ let private ResolveExprDotLongIdent (ncenv: NameResolver) m ad nenv ty (id: Iden
| _ ->
ForceRaise adhocDotSearchAccessible

/// Returns a pair (itemRange, itemIdentRange):
///
/// * `itemRange` is the structural range of the long identifier as consumed
/// by resolution — the whole-long-id span when `rest = []`, or the range
/// over the consumed prefix when `rest <> []`. This is what typed-tree
/// construction uses for expression ranges and sequence points, so we
/// never narrow it.
/// * `itemIdentRange` is the terminal identifier's own source range — the
/// piece the user perceives as "this item's name". It is used for
/// diagnostics and for sink-reported symbol ranges (Find Usages, symbol
/// highlight, FSharpSymbolUse) so error / IDE UI hits only the resolved
/// name. Fixes #14284 and #3920.
///
/// For `T.Instance.Method("")`:
/// itemRange = `T.Instance.Method`
/// itemIdentRange = `Method`
let ComputeItemRange wholem (lid: Ident list) rest =
match rest with
| [] -> wholem
| _ ->
let ids = List.truncate (max 0 (lid.Length - rest.Length)) lid
match ids with
let itemRange =
match rest with
| [] -> wholem
| _ -> rangeOfLid ids
| _ ->
let ids = List.truncate (max 0 (lid.Length - rest.Length)) lid
match ids with
| [] -> wholem
| _ -> rangeOfLid ids
let itemIdentRange =
match rest, lid with
| [], _ :: _ -> (List.last lid).idRange
| _ -> itemRange
itemRange, itemIdentRange

/// Filters method groups that will be sent to Visual Studio IntelliSense
/// to include only static/instance members
Expand Down Expand Up @@ -4215,7 +4237,7 @@ let ResolveLongIdentAsExprAndComputeRange (sink: TcResultsSink) (ncenv: NameReso
match ResolveExprLongIdent sink ncenv wholem ad nenv typeNameResInfo lid maybeAppliedArgExpr with
| Exception e -> Exception e
| Result (tinstEnclosing, item1, rest) ->
let itemRange = ComputeItemRange wholem lid rest
let itemRange, itemIdentRange = ComputeItemRange wholem lid rest

let item = FilterMethodGroups ncenv itemRange item1 true

Expand All @@ -4241,13 +4263,16 @@ let ResolveLongIdentAsExprAndComputeRange (sink: TcResultsSink) (ncenv: NameReso
| Item.ActivePatternResult _ -> ItemOccurrence.Binding
| _ -> ItemOccurrence.Use

CallMethodGroupNameResolutionSink sink (itemRange, nenv, refinedItem, item, tpinst, occurrence, ad)
// Use the narrow terminal-identifier range for the sink so that
// FSharpSymbolUse / Find Usages / symbol-highlight surfaces report
// only on the name the user perceives as the item's name, not on
// the full long-id span. See #3920, #14284.
CallMethodGroupNameResolutionSink sink (itemIdentRange, nenv, refinedItem, item, tpinst, occurrence, ad)

// #16621
match refinedItem with
| Item.Property(_, pinfos, _) ->
let propIdentRange = if rest.IsEmpty then (List.last lid).idRange else itemRange
RegisterUnionCaseTesterForProperty sink propIdentRange pinfos
RegisterUnionCaseTesterForProperty sink itemIdentRange pinfos
| _ -> ()

let callSinkWithSpecificOverload (minfo: MethInfo, pinfoOpt: PropInfo option, tpinst) =
Expand All @@ -4267,14 +4292,14 @@ let ResolveLongIdentAsExprAndComputeRange (sink: TcResultsSink) (ncenv: NameReso
AfterResolution.RecordResolution(None, (fun tpinst -> callSink(item, tpinst)), callSinkWithSpecificOverload, (fun () -> callSink (item, emptyTyparInst)))

elif isWrongItemInExpr item then
CallNameResolutionSink sink (itemRange, nenv, item, emptyTyparInst, ItemOccurrence.InvalidUse, ad)
CallNameResolutionSink sink (itemIdentRange, nenv, item, emptyTyparInst, ItemOccurrence.InvalidUse, ad)
AfterResolution.DoNothing

else
callSink (item, emptyTyparInst)
AfterResolution.DoNothing

success (tinstEnclosing, item, itemRange, rest, afterResolution)
success (tinstEnclosing, item, itemRange, itemIdentRange, rest, afterResolution)

[<return: Struct>]
let (|NonOverridable|_|) namedItem =
Expand All @@ -4292,11 +4317,11 @@ let ResolveExprDotLongIdentAndComputeRange (sink: TcResultsSink) (ncenv: NameRes
| id :: rest ->
ResolveExprDotLongIdent ncenv wholem ad nenv ty id rest typeNameResInfo findFlag maybeAppliedArgExpr
| _ -> error(InternalError("ResolveExprDotLongIdentAndComputeRange", wholem))
let itemRange = ComputeItemRange wholem lid rest
resInfo, item, rest, itemRange
let itemRange, itemIdentRange = ComputeItemRange wholem lid rest
resInfo, item, rest, itemRange, itemIdentRange

// "true" resolution
let resInfo, item, rest, itemRange = resolveExpr findFlag
let resInfo, item, rest, itemRange, itemIdentRange = resolveExpr findFlag
ResolutionInfo.SendEntityPathToSink(sink, ncenv, nenv, ItemOccurrence.Use, ad, resInfo, ResultTyparChecker(fun () -> CheckAllTyparsInferrable ncenv.amap itemRange item))

// Record the precise resolution of the field for intellisense/goto definition
Expand All @@ -4305,25 +4330,26 @@ let ResolveExprDotLongIdentAndComputeRange (sink: TcResultsSink) (ncenv: NameRes
| None -> AfterResolution.DoNothing // do not refine the resolution if nobody listens
| Some _ ->
// resolution for goto definition
let unrefinedItem, itemRange, overrides =
let unrefinedItem, itemRange, itemIdentRange, overrides =
match findFlag, item with
| FindMemberFlag.PreferOverrides, _
| _, NonOverridable() -> item, itemRange, false
| _, NonOverridable() -> item, itemRange, itemIdentRange, false
| FindMemberFlag.IgnoreOverrides, _
| FindMemberFlag.DiscardOnFirstNonOverride, _ ->
let _, item, _, itemRange = resolveExpr FindMemberFlag.PreferOverrides
item, itemRange, true
let _, item, _, itemRange, itemIdentRange = resolveExpr FindMemberFlag.PreferOverrides
item, itemRange, itemIdentRange, true

let callSink (refinedItem, tpinst) =
let refinedItem = FilterMethodGroups ncenv itemRange refinedItem staticOnly
let unrefinedItem = FilterMethodGroups ncenv itemRange unrefinedItem staticOnly
CallMethodGroupNameResolutionSink sink (itemRange, nenv, refinedItem, unrefinedItem, tpinst, ItemOccurrence.Use, ad)
// Use narrow terminal-identifier range for the sink (FSharpSymbolUse / Find Usages
// / symbol highlight). See #3920, #14284.
CallMethodGroupNameResolutionSink sink (itemIdentRange, nenv, refinedItem, unrefinedItem, tpinst, ItemOccurrence.Use, ad)

// #16621
match refinedItem with
| Item.Property(_, pinfos, _) ->
let propIdentRange = if rest.IsEmpty then (List.last lid).idRange else itemRange
RegisterUnionCaseTesterForProperty sink propIdentRange pinfos
RegisterUnionCaseTesterForProperty sink itemIdentRange pinfos
| _ -> ()

let callSinkWithSpecificOverload (minfo: MethInfo, pinfoOpt: PropInfo option, tpinst) =
Expand All @@ -4344,7 +4370,7 @@ let ResolveExprDotLongIdentAndComputeRange (sink: TcResultsSink) (ncenv: NameRes
callSink (unrefinedItem, emptyTyparInst)
AfterResolution.DoNothing

item, itemRange, rest, afterResolution
item, itemRange, itemIdentRange, rest, afterResolution


//-------------------------------------------------------------------------
Expand Down
14 changes: 12 additions & 2 deletions src/Compiler/Checking/NameResolution.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,11 @@ val internal ResolvePartialLongIdentToClassOrRecdFields:
val internal ResolveRecordOrClassFieldsOfType: NameResolver -> range -> AccessorDomain -> TType -> bool -> Item list

/// Resolve a long identifier occurring in an expression position.
///
/// Returns the structural `range` (the whole long-identifier span used for
/// typed-tree construction) and a narrow `range` — the terminal identifier's
/// own source range — for use in diagnostics and symbol-use reporting
/// (see #14284, #3920).
val internal ResolveLongIdentAsExprAndComputeRange:
sink: TcResultsSink ->
ncenv: NameResolver ->
Expand All @@ -884,9 +889,14 @@ val internal ResolveLongIdentAsExprAndComputeRange:
typeNameResInfo: TypeNameResolutionInfo ->
lid: Ident list ->
maybeAppliedArgExpr: SynExpr option ->
ResultOrException<EnclosingTypeInst * Item * range * Ident list * AfterResolution>
ResultOrException<EnclosingTypeInst * Item * range * range * Ident list * AfterResolution>

/// Resolve a long identifier occurring in an expression position, qualified by a type.
///
/// Returns the structural `range` (the whole long-identifier span used for
/// typed-tree construction) and a narrow `range` — the terminal identifier's
/// own source range — for use in diagnostics and symbol-use reporting
/// (see #14284, #3920).
val internal ResolveExprDotLongIdentAndComputeRange:
sink: TcResultsSink ->
ncenv: NameResolver ->
Expand All @@ -899,7 +909,7 @@ val internal ResolveExprDotLongIdentAndComputeRange:
findFlag: FindMemberFlag ->
staticOnly: bool ->
maybeAppliedArgExpr: SynExpr option ->
Item * range * Ident list * AfterResolution
Item * range * range * Ident list * AfterResolution

/// A generator of type instantiations used when no more specific type instantiation is known.
val FakeInstantiationGenerator: range -> Typar list -> TType list
Expand Down
18 changes: 17 additions & 1 deletion src/Compiler/Service/ServiceParamInfoLocations.fs
Original file line number Diff line number Diff line change
Expand Up @@ -466,13 +466,29 @@ module internal SynExprAppLocationsImpl =
| _ -> None, Some inner

let getAllCurriedArgsAtPosition pos parseTree =
// Finds the leaf (non-App) function expression in a curried application chain.
// E.g. for App(App(LongIdent([M;f]), arg1), arg2) returns LongIdent([M;f]).
let rec getLeafFuncExpr =
function
| SynExpr.App(funcExpr = funcExpr) -> getLeafFuncExpr funcExpr
| expr -> expr

SyntaxTraversal.Traverse(
pos,
parseTree,
{ new SyntaxVisitorBase<_>() with
member _.VisitExpr(_path, traverseSynExpr, defaultTraverse, expr) =
match expr with
| SynExpr.App(_exprAtomicFlag, _isInfix, funcExpr, argExpr, range) when posEq pos range.Start ->
// After symbol-use range narrowing for dotted accesses (e.g. M.f, obj.Method),
// pos may point at the terminal identifier rather than the full long-id start.
// The second condition handles this by checking whether pos falls within the
// leaf function expression's range in the application chain.
// We exclude infix applications from the fallback to avoid matching operator
// uses like "a === b" where pos points at the operator.
| SynExpr.App(_exprAtomicFlag, isInfix, funcExpr, argExpr, range) when
posEq pos range.Start
|| (not isInfix && rangeContainsPos (getLeafFuncExpr funcExpr).Range pos)
->
let isInfixFuncExpr =
match funcExpr with
| SynExpr.App(_, isInfix, _, _, _) -> isInfix
Expand Down
Loading
Loading