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
Expand Up @@ -3,6 +3,7 @@
* Fix attributes on return type of unparenthesized tuple methods being silently dropped from IL. ([Issue #462](https://github.com/dotnet/fsharp/issues/462), [PR #19714](https://github.com/dotnet/fsharp/pull/19714))
* Fix internal error FS0073 "Undefined or unsolved type variable" in IlxGen when nested inline SRTP functions with multiple overloads leave unsolved typars in the non-witness codegen path. ([Issue #19709](https://github.com/dotnet/fsharp/issues/19709), [PR #19710](https://github.com/dotnet/fsharp/pull/19710))
* 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 @@ -8632,7 +8632,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 (tinstEnclosing, item, mItem, _, rest, afterRes)
when
(match item with
| Item.DelegateCtor _
Expand All @@ -8643,7 +8643,7 @@ and TcNameOfExpr (cenv: cenv) env tpenv (synArg: SynExpr) =
| _ -> true
| _ -> true) ->
let overallTy = match overallTyOpt with None -> MustEqual (NewInferenceType g) | Some t -> t
let _, _ = TcItemThen cenv overallTy env tpenv res None delayed
let _, _ = TcItemThen cenv overallTy env tpenv (tinstEnclosing, item, mItem, rest, afterRes) None delayed
true
| _ ->
false
Expand Down Expand Up @@ -8870,11 +8870,11 @@ and TcLongIdentThen (cenv: cenv) (overallTy: OverallTy) env tpenv (SynLongIdent(

let ad = env.eAccessRights
let typeNameResInfo = GetLongIdentTypeNameInfo delayed
let nameResolutionResult =
let (tinstEnclosing, item, mItem, _, rest, afterResolution) =
let maybeAppliedArgExpr = DelayedItem.maybeAppliedArgForPreferExtensionOverProperty delayed
ResolveLongIdentAsExprAndComputeRange cenv.tcSink cenv.nameResolver (rangeOfLid longId) ad env.eNameResEnv typeNameResInfo longId maybeAppliedArgExpr
|> ForceRaise
TcItemThen cenv overallTy env tpenv nameResolutionResult None delayed
TcItemThen cenv overallTy env tpenv (tinstEnclosing, item, mItem, rest, afterResolution) None delayed

//-------------------------------------------------------------------------
// Typecheck "item+projections"
Expand Down Expand Up @@ -9121,7 +9121,7 @@ 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
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

| DelayedTypeApp(tyargs, _mTypeArgs, mExprAndTypeArgs) :: _delayed' ->
Expand Down Expand Up @@ -9721,7 +9721,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 @@ -10463,6 +10463,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
Comment on lines +10467 to +10468
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.

Let's narrow it to Method and don't add error-prone range calculations, please?

// 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
73 changes: 50 additions & 23 deletions src/Compiler/Checking/NameResolution.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4194,14 +4194,37 @@ 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 used downstream in
/// `TcItemThen` for typed-tree expression ranges, sequence points, and
/// `delayRest` computations, 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 only inside
/// the name-resolution sink callbacks (above) for diagnostics and IDE
/// symbol-use reporting (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` (consumed by TcItemThen for tree construction)
/// itemIdentRange = `Method` (reported to IDE sinks only)
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 @@ -4249,7 +4272,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 @@ -4275,13 +4298,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 @@ -4301,14 +4327,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 @@ -4326,11 +4352,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 @@ -4339,25 +4365,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 @@ -4378,7 +4405,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 @@ -878,6 +878,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 @@ -887,9 +892,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 @@ -902,7 +912,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