-
Notifications
You must be signed in to change notification settings - Fork 864
Fix semantic classification errors for generics, slices, CEs, and open type #19939
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6030ae5
be287cf
a09e9bc
371a1bd
d0744bb
9eb9e34
4399ff3
f4a2da9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6048,9 +6048,14 @@ and TcExprUndelayed (cenv: cenv) (overallTy: OverallTy) env tpenv (synExpr: SynE | |
| // knows this is provably non-null (even for AllowNullLiteral types). | ||
| let objTy = if g.checkNullness then replaceNullnessOfTy Nullness.KnownFromConstructor objTy else objTy | ||
|
|
||
| // Use the narrow leading-identifier range for the constructor item so the classifier paints | ||
| // only the type name as ConstructorForReferenceType/DisposableType, not the surrounding | ||
| // `<...>` type-arg punctuation (#19905 item 4). | ||
| let mObjTy = synTypeLeadingIdentRange synObjTy | ||
|
|
||
| TcNonControlFlowExpr env <| fun env -> | ||
| TcPropagatingExprLeafThenConvert cenv overallTy objTy env (* true *) mNewExpr (fun () -> | ||
| TcNewExpr cenv env tpenv objTy (Some synObjTy.Range) superInit arg mNewExpr | ||
| TcNewExpr cenv env tpenv objTy (Some mObjTy) superInit arg mNewExpr | ||
| ) | ||
|
|
||
| | SynExpr.ObjExpr (synObjTy, argopt, _mWith, binds, members, extraImpls, mNewExpr, m) -> | ||
|
|
@@ -6772,7 +6777,7 @@ and ExpandIndexArgs (cenv: cenv) (synLeftExprOpt: SynExpr option) indexArgs = | |
| | Some (a2, isFromEnd2) -> | ||
| yield mkSynSomeExpr range2 (if isFromEnd2 then rewriteReverseExpr pos a2 range2 else a2) | ||
| | None -> | ||
| yield mkSynNoneExpr range1 | ||
| yield mkSynNoneExpr range2 | ||
| ] | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Correctness — Low] The However, none of the 13 new regression tests exercises this path (they all assert absence of classification rather than presence of a correctly-positioned diagnostic). If a regression here reintroduced |
||
| ) | ||
| |> List.collect id | ||
|
|
@@ -6926,11 +6931,18 @@ and TcIndexingThen cenv env overallTy mWholeExpr mDot tpenv setInfo synLeftExprO | |
| match propName with | ||
| | None -> "Item" | ||
| | Some nm -> nm | ||
| // For slice expressions (not regular indexing), the synthesized GetSlice/SetSlice | ||
| // identifier has no user-visible source range. Use a synthetic range so the name | ||
| // resolution sink (NotifyNameResolution) drops the CNR, otherwise the semantic | ||
| // classifier paints the entire `list[...]` span as Method (#19905 item 5). | ||
| // Regular indexing (`x.[n]`/`x[n]`) keeps the visible range so find-all-references | ||
| // continues to surface implicit Item indexer usages. | ||
| let mNm = if isIndex then mWholeExpr else mWholeExpr.MakeSynthetic() | ||
| let delayed = | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Find-All-References regression — Medium] Using Concretely, on a type with Suggested alternatives: (a) keep |
||
| match setInfo with | ||
| // expr1.[expr2] | ||
| | None -> | ||
| [ DelayedDotLookup([ ident(nm, mWholeExpr)], mWholeExpr) | ||
| [ DelayedDotLookup([ ident(nm, mNm)], mNm) | ||
| DelayedApp(ExprAtomicFlag.Atomic, true, synLeftExprOpt, MakeIndexParam None, mWholeExpr) | ||
| yield! delayed ] | ||
|
|
||
|
|
@@ -6942,7 +6954,8 @@ and TcIndexingThen cenv env overallTy mWholeExpr mDot tpenv setInfo synLeftExprO | |
| MakeDelayedSet(expr3, mWholeExpr) | ||
| yield! delayed ] | ||
| else | ||
| [ DelayedDotLookup([ident("SetSlice", mOfLeftOfSet)], mOfLeftOfSet) | ||
| let mSynNm = mOfLeftOfSet.MakeSynthetic() | ||
| [ DelayedDotLookup([ident("SetSlice", mSynNm)], mSynNm) | ||
| DelayedApp(ExprAtomicFlag.Atomic, true, synLeftExprOpt, MakeIndexParam (Some expr3), mWholeExpr) | ||
| yield! delayed ] | ||
|
|
||
|
|
@@ -9154,24 +9167,31 @@ and TcTypeItemThen (cenv: cenv) overallTy env nm ty tpenv mItem tinstEnclosing d | |
| let g = cenv.g | ||
| let ad = env.eAccessRights | ||
| match delayed with | ||
| | DelayedTypeApp(tyargs, _mTypeArgs, mExprAndTypeArgs) :: DelayedDotLookup (longId, mLongId) :: otherDelayed -> | ||
| | DelayedTypeApp(tyargs, _mTypeArgs, mExprAndTypeArgs) :: DelayedDotLookup (longId, _mLongId) :: otherDelayed -> | ||
| // If Item.Types is returned then the ty will be of the form TType_app(tcref, genericTyargs) where tyargs | ||
| // is a fresh instantiation for tcref. TcNestedTypeApplication will chop off precisely #genericTyargs args | ||
| // and replace them by 'tyargs' | ||
| let ty, tpenv = TcNestedTypeApplication cenv NewTyparsOK CheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes env tpenv mExprAndTypeArgs ty tinstEnclosing tyargs | ||
|
|
||
| // Report information about the whole expression including type arguments to VS | ||
| // Report information about the type to VS. Use the narrow mItem (the type identifier only) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not specific to VS. |
||
| // so the classifier does not paint the surrounding `<...>` type-arg punctuation as part of the | ||
| // type classification (#19905 item 6). | ||
| let item = Item.Types(nm, [ty]) | ||
| CallNameResolutionSink cenv.tcSink (mExprAndTypeArgs, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, env.eAccessRights) | ||
| CallNameResolutionSink cenv.tcSink (mItem, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, env.eAccessRights) | ||
| let typeNameResInfo = GetLongIdentTypeNameInfo otherDelayed | ||
| let item, mItem, mItemIdent, rest, afterResolution = ResolveExprDotLongIdentAndComputeRange cenv.tcSink cenv.nameResolver (unionRanges mExprAndTypeArgs mLongId) ad env.eNameResEnv ty longId typeNameResInfo IgnoreOverrides true None | ||
| // Resolve `.longId` using only the longId range as wholem, so the resulting mItem / Method CNR | ||
| // does not extend back across the `<...>` type-arg span (#19905 item 6). | ||
| let item, mItem, mItemIdent, rest, afterResolution = | ||
| ResolveExprDotLongIdentAndComputeRange cenv.tcSink cenv.nameResolver (rangeOfLid longId) 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! | ||
| let ty, _ = TcNestedTypeApplication cenv NewTyparsOK CheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes env tpenv mExprAndTypeArgs ty tinstEnclosing tyargs | ||
| let item = Item.Types(nm, [ty]) | ||
| CallNameResolutionSink cenv.tcSink (mExprAndTypeArgs, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, env.eAccessRights) | ||
| // Use the narrow mItem range so the type classification does not paint the surrounding | ||
| // `<...>` type-arg punctuation (#19905 items 4/6). | ||
|
Comment on lines
+9192
to
+9193
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please cleanup. |
||
| CallNameResolutionSink cenv.tcSink (mItem, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, env.eAccessRights) | ||
|
|
||
| // Same error as in the following case | ||
| error(Error(FSComp.SR.tcInvalidUseOfTypeName(), mItem)) | ||
|
|
@@ -9217,7 +9237,10 @@ and TcMethodItemThen (cenv: cenv) overallTy env item methodName minfos tpenv mIt | |
| // FUTURE: can we do better than emptyTyparInst here, in order to display instantiations | ||
| // of type variables in the quick info provided in the IDE? But note we haven't yet even checked if the | ||
| // number of type arguments is correct... | ||
| CallNameResolutionSink cenv.tcSink (mExprAndTypeArgs, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, env.eAccessRights) | ||
| // Note: we deliberately use the narrow mItem range here (the identifier-only range), not | ||
| // mExprAndTypeArgs, so the semantic classifier paints only the method identifier as a Method | ||
| // and not the surrounding `<...>` type-arg punctuation (#19905 item 3). | ||
| CallNameResolutionSink cenv.tcSink (mItem, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, env.eAccessRights) | ||
|
|
||
| match otherDelayed with | ||
| | DelayedApp(atomicFlag, _, _, arg, mExprAndArg) :: otherDelayed -> | ||
|
|
@@ -9251,6 +9274,11 @@ and TcCtorItemThen (cenv: cenv) overallTy env item nm minfos tinstEnclosing tpen | |
| | DelayedTypeApp(tyargs, _mTypeArgs, mExprAndTypeArgs) :: DelayedApp(_, _, _, arg, mExprAndArg) :: otherDelayed -> | ||
|
|
||
| let objTyAfterTyArgs, tpenv = TcNestedTypeApplication cenv NewTyparsOK CheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes env tpenv mExprAndTypeArgs objTy tinstEnclosing tyargs | ||
| // Mirror the non-generic ctor path above: use mExprAndArg so the expression-typing entry | ||
| // used for Quick Info / dot-completion (GetExprTypingForPosition) reflects the full | ||
| // `Foo<...>(arg)` expression, not just the identifier. The narrow semantic-classification | ||
| // CNR for #19905 item 4 is emitted via CallNameResolutionSink in the DelayedTypeApp-only | ||
| // branch below. | ||
| CallExprHasTypeSink cenv.tcSink (mExprAndArg, env.NameEnv, objTyAfterTyArgs, env.eAccessRights) | ||
| let itemAfterTyArgs, minfosAfterTyArgs = | ||
| #if !NO_TYPEPROVIDERS | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Inconsistency — Low] This generic-ctor branch now narrows | DelayedApp(_, _, _, arg, mExprAndArg) :: otherDelayed ->
CallExprHasTypeSink cenv.tcSink (mExprAndArg, env.NameEnv, objTy, env.eAccessRights)Functionally this is safe for IntelliSense completion because
Either narrow the non-generic branch for symmetry, or leave both at the wider range and only narrow the |
||
|
|
@@ -9274,8 +9302,10 @@ and TcCtorItemThen (cenv: cenv) overallTy env item nm minfos tinstEnclosing tpen | |
| let objTy, tpenv = TcNestedTypeApplication cenv NewTyparsOK CheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes env tpenv mExprAndTypeArgs objTy tinstEnclosing tyargs | ||
|
|
||
| // A case where we have an incomplete name e.g. 'Foo<int>.' - we still want to report it to VS! | ||
| // Use the narrow mItem range (the ctor identifier only) so the classifier does not paint the | ||
| // surrounding `<...>` type-arg punctuation as part of the type/ctor classification (#19905 item 4/6). | ||
| let resolvedItem = Item.Types(nm, [objTy]) | ||
| CallNameResolutionSink cenv.tcSink (mExprAndTypeArgs, env.NameEnv, resolvedItem, emptyTyparInst, ItemOccurrence.Use, env.eAccessRights) | ||
| CallNameResolutionSink cenv.tcSink (mItem, env.NameEnv, resolvedItem, emptyTyparInst, ItemOccurrence.Use, env.eAccessRights) | ||
|
|
||
| minfos |> List.iter (fun minfo -> UnifyTypes cenv env mExprAndTypeArgs minfo.ApparentEnclosingType objTy) | ||
| TcCtorCall true cenv env tpenv overallTy objTy (Some mItemIdent) item false [] mExprAndTypeArgs otherDelayed (Some afterResolution) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -178,29 +178,36 @@ module TcResolutionsExtensions = | |
| | _ -> None | ||
| | _ -> None | ||
|
|
||
| // Custom builders like 'async { }' are both Item.Value and Item.CustomBuilder. | ||
| // We should prefer the latter, otherwise they would not get classified as CEs. | ||
| // Custom builders like 'async { }' are both Item.Value and Item.CustomBuilder | ||
| // (and sometimes additional Item.CustomOperation entries inside a list/array | ||
| // comprehension's CE-machinery). We should prefer any CustomBuilder/CustomOperation | ||
| // hit over the Value one so the builder name gets a ComputationExpression | ||
| // classification instead of being painted as a local value. | ||
| let takeCustomBuilder (cnrs: CapturedNameResolution[]) = | ||
| assert (cnrs.Length > 0) | ||
|
|
||
| if cnrs.Length = 1 then | ||
| cnrs | ||
| elif cnrs.Length = 2 then | ||
| match cnrs[0].Item, cnrs[1].Item with | ||
| | Item.Value _, Item.CustomBuilder _ -> [| cnrs[1] |] | ||
| | Item.CustomBuilder _, Item.Value _ -> [| cnrs[0] |] | ||
| | _ -> cnrs | ||
| else | ||
| let preferred = | ||
| cnrs | ||
| |> Array.filter (fun cnr -> | ||
| match cnr.Item with | ||
| | Item.CustomBuilder _ | ||
| | Item.CustomOperation _ -> true | ||
| | _ -> false) | ||
|
|
||
| if preferred.Length > 0 then preferred else cnrs | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Behavioural change — Low] The previous code's The semantic classifier's dedup is keyed only by range, so only one classification ever wins per range anyway — but the which one changes from 'insertion-order first' to 'first CB/CO in insertion order'. That's the intended fix for item 2 (the CE-inside-comprehension case where |
||
|
|
||
| let resolutions = | ||
| let groupAndPickBuilder (cnrs: CapturedNameResolution[]) = | ||
| cnrs | ||
| |> Array.groupBy (fun cnr -> cnr.Range) | ||
| |> Array.collect (fun (_, cnrs) -> takeCustomBuilder cnrs) | ||
|
|
||
| match range with | ||
| | Some range -> | ||
| sResolutions.CapturedNameResolutions.ToArray() | ||
| |> Array.filter (fun cnr -> rangeContainsPos range cnr.Range.Start || rangeContainsPos range cnr.Range.End) | ||
| |> Array.groupBy (fun cnr -> cnr.Range) | ||
| |> Array.collect (fun (_, cnrs) -> takeCustomBuilder cnrs) | ||
| | None -> sResolutions.CapturedNameResolutions.ToArray() | ||
| |> groupAndPickBuilder | ||
| | None -> sResolutions.CapturedNameResolutions.ToArray() |> groupAndPickBuilder | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [IDE Performance — Low] Previously, when For large files this is O(N) extra work plus extra array allocations on every full-file classify. Functionally an improvement (CE builders now classify correctly file-wide instead of only inside a sub-range), but please measure |
||
|
|
||
| let duplicates = HashSet<range>(comparer) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -273,20 +273,27 @@ module UnusedOpens = | |
| /// Async to allow cancellation. | ||
| let filterOpenStatements (symbolUses1: FSharpSymbolUse[], symbolUses2: FSharpSymbolUse[]) openStatements = | ||
| async { | ||
| // the key is a namespace or module, the value is a list of FSharpSymbolUse range of symbols defined in the | ||
| // namespace or module. So, it's just symbol uses ranges grouped by namespace or module where they are _defined_. | ||
| // the key is a namespace, module, or type entity, the value is a list of FSharpSymbolUse range of symbols | ||
| // defined in that entity. So, it's just symbol uses ranges grouped by the entity where they are _defined_. | ||
| // Type entities are kept here so that `open type T` is recognised as used when its imported members are. | ||
| // `symbolUses2` still flows through the RevealedSymbols path below, so this is purely additive. | ||
| let symbolUsesRangesByDeclaringEntity = | ||
| Dictionary<FSharpEntity, range list>(entityHash) | ||
|
|
||
| for symbolUse in symbolUses1 do | ||
| let recordByDeclaringEntity (symbolUse: FSharpSymbolUse) = | ||
| match symbolUse.Symbol with | ||
| | :? FSharpMemberOrFunctionOrValue as f -> | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Correctness / Possible Incomplete Fix — Medium] The generalised The item-7 regression test added in this PR uses a static method ( Please (a) add an explicit test mirroring the issue verbatim: open type System.Math
let _ = PIand (b) if it fails, extend |
||
| match f.DeclaringEntity with | ||
| | Some entity when entity.IsNamespace || entity.IsFSharpModule -> | ||
| symbolUsesRangesByDeclaringEntity.BagAdd(entity, symbolUse.Range) | ||
| | _ -> () | ||
| | Some entity -> symbolUsesRangesByDeclaringEntity.BagAdd(entity, symbolUse.Range) | ||
| | None -> () | ||
| | _ -> () | ||
|
|
||
| for symbolUse in symbolUses1 do | ||
| recordByDeclaringEntity symbolUse | ||
|
|
||
| for symbolUse in symbolUses2 do | ||
| recordByDeclaringEntity symbolUse | ||
|
|
||
| let! results = | ||
| filterOpenStatementsIncremental | ||
| symbolUses2 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please cleanup.