Skip to content

Commit 1a246cc

Browse files
vladimalatkin
authored andcommitted
Completion in object initializers
fixes dotnet#119 closes dotnet#197 commit d4dd838 Author: Vladimir Matveev <vladima@microsoft.com> Date: Tue Feb 3 16:20:52 2015 -0800 added test for settable extension property commit 5edc7cc Author: Vladimir Matveev <vladima@microsoft.com> Date: Tue Feb 3 16:20:26 2015 -0800 addressed CR feedback commit f17c57d Author: v2m <desco.by@gmail.com> Date: Sun Feb 1 14:55:02 2015 -0800 added tests for property completion in object creation expressions commit 38a0734 Author: v2m <desco.by@gmail.com> Date: Wed Jan 28 11:25:09 2015 -0800 initial revision of completions for properties in 'new' expressions
1 parent ea11a9b commit 1a246cc

File tree

6 files changed

+467
-206
lines changed

6 files changed

+467
-206
lines changed

src/fsharp/nameres.fs

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2459,8 +2459,17 @@ let ResolveRecordOrClassFieldsOfType (ncenv: NameResolver) m ad typ statics =
24592459
|> List.filter (fun rfref -> rfref.IsStatic = statics && IsFieldInfoAccessible ad rfref)
24602460
|> List.map Item.RecdField
24612461

2462+
[<RequireQualifiedAccess>]
2463+
type ResolveCompletionTargets =
2464+
| All of (MethInfo -> TType -> bool)
2465+
| SettablePropertiesAndFields
2466+
member this.ResolveAll =
2467+
match this with
2468+
| All _ -> true
2469+
| SettablePropertiesAndFields -> false
2470+
24622471
/// Resolve a (possibly incomplete) long identifier to a set of possible resolutions, qualified by type.
2463-
let ResolveCompletionsInType (ncenv: NameResolver) nenv isApplicableMeth m ad statics typ =
2472+
let ResolveCompletionsInType (ncenv: NameResolver) nenv (completionTargets: ResolveCompletionTargets) m ad statics typ =
24642473
let g = ncenv.g
24652474
let amap = ncenv.amap
24662475

@@ -2469,21 +2478,23 @@ let ResolveCompletionsInType (ncenv: NameResolver) nenv isApplicableMeth m ad st
24692478
|> List.filter (fun rfref -> rfref.IsStatic = statics && IsFieldInfoAccessible ad rfref)
24702479

24712480
let ucinfos =
2472-
if statics && isAppTy g typ then
2481+
if completionTargets.ResolveAll && statics && isAppTy g typ then
24732482
let tc,tinst = destAppTy g typ
24742483
tc.UnionCasesAsRefList
24752484
|> List.filter (IsUnionCaseUnseen ad g ncenv.amap m >> not)
24762485
|> List.map (fun ucref -> Item.UnionCase(UnionCaseInfo(tinst,ucref),false))
24772486
else []
24782487

24792488
let einfos =
2480-
ncenv.InfoReader.GetEventInfosOfType(None,ad,m,typ)
2481-
|> List.filter (fun x ->
2482-
IsStandardEventInfo ncenv.InfoReader m ad x &&
2483-
x.IsStatic = statics)
2489+
if completionTargets.ResolveAll then
2490+
ncenv.InfoReader.GetEventInfosOfType(None,ad,m,typ)
2491+
|> List.filter (fun x ->
2492+
IsStandardEventInfo ncenv.InfoReader m ad x &&
2493+
x.IsStatic = statics)
2494+
else []
24842495

24852496
let nestedTypes =
2486-
if statics then
2497+
if completionTargets.ResolveAll && statics then
24872498
typ
24882499
|> GetNestedTypesOfType (ad, ncenv, None, TypeNameResolutionStaticArgsInfo.Indefinite, false, m)
24892500
else
@@ -2501,7 +2512,6 @@ let ResolveCompletionsInType (ncenv: NameResolver) nenv isApplicableMeth m ad st
25012512
x.IsStatic = statics &&
25022513
IsPropInfoAccessible g amap m ad x)
25032514

2504-
25052515
// Exclude get_ and set_ methods accessed by properties
25062516
let pinfoMethNames =
25072517
(pinfosIncludingUnseen
@@ -2513,13 +2523,15 @@ let ResolveCompletionsInType (ncenv: NameResolver) nenv isApplicableMeth m ad st
25132523
|> List.map (fun pinfo -> pinfo.SetterMethod.LogicalName))
25142524

25152525
let einfoMethNames =
2516-
[ for einfo in einfos do
2517-
let delegateType = einfo.GetDelegateType(amap,m)
2518-
let (SigOfFunctionForDelegate(invokeMethInfo,_,_,_)) = GetSigOfFunctionForDelegate ncenv.InfoReader delegateType m ad
2519-
// Only events with void return types are suppressed in intellisense.
2520-
if slotSigHasVoidReturnTy (invokeMethInfo.GetSlotSig(amap, m)) then
2521-
yield einfo.GetAddMethod().DisplayName
2522-
yield einfo.GetRemoveMethod().DisplayName ]
2526+
if completionTargets.ResolveAll then
2527+
[ for einfo in einfos do
2528+
let delegateType = einfo.GetDelegateType(amap,m)
2529+
let (SigOfFunctionForDelegate(invokeMethInfo,_,_,_)) = GetSigOfFunctionForDelegate ncenv.InfoReader delegateType m ad
2530+
// Only events with void return types are suppressed in intellisense.
2531+
if slotSigHasVoidReturnTy (invokeMethInfo.GetSlotSig(amap, m)) then
2532+
yield einfo.GetAddMethod().DisplayName
2533+
yield einfo.GetRemoveMethod().DisplayName ]
2534+
else []
25232535

25242536
let suppressedMethNames = Zset.ofList String.order (pinfoMethNames @ einfoMethNames)
25252537

@@ -2528,6 +2540,10 @@ let ResolveCompletionsInType (ncenv: NameResolver) nenv isApplicableMeth m ad st
25282540
|> List.filter (fun x -> not (PropInfoIsUnseen m x))
25292541

25302542
let minfoFilter (minfo:MethInfo) =
2543+
let isApplicableMeth =
2544+
match completionTargets with
2545+
| ResolveCompletionTargets.All x -> x
2546+
| _ -> failwith "internal error: expected completionTargets = ResolveCompletionTargets.All"
25312547
// Only show the Finalize, MemberwiseClose etc. methods on System.Object for values whose static type really is
25322548
// System.Object. Few of these are typically used from F#.
25332549
//
@@ -2566,24 +2582,37 @@ let ResolveCompletionsInType (ncenv: NameResolver) nenv isApplicableMeth m ad st
25662582
result
25672583

25682584
let pinfoItems =
2585+
let pinfos =
2586+
match completionTargets with
2587+
| ResolveCompletionTargets.SettablePropertiesAndFields -> pinfos |> List.filter (fun p -> p.HasSetter)
2588+
| _ -> pinfos
2589+
25692590
pinfos
2570-
|> List.map (fun pinfo -> DecodeFSharpEvent [pinfo] ad g ncenv m)
2571-
|> List.filter (fun pinfo-> match pinfo with
2572-
| Some(Item.Event(einfo)) -> IsStandardEventInfo ncenv.InfoReader m ad einfo
2573-
| _ -> pinfo.IsSome)
2574-
|> List.map (fun pinfo->pinfo.Value)
2575-
2576-
let addersAndRemovers =
2577-
pinfoItems
2578-
|> List.map (function Item.Event(FSEvent(_,_,addValRef,removeValRef)) -> [addValRef.LogicalName;removeValRef.LogicalName] | _ -> [])
2579-
|> List.concat
2580-
2591+
|> List.choose (fun pinfo->
2592+
let pinfoOpt = DecodeFSharpEvent [pinfo] ad g ncenv m
2593+
match pinfoOpt, completionTargets with
2594+
| Some(Item.Event(einfo)), ResolveCompletionTargets.All _ -> if IsStandardEventInfo ncenv.InfoReader m ad einfo then pinfoOpt else None
2595+
| _ -> pinfoOpt)
2596+
25812597
// REVIEW: add a name filter here in the common cases?
25822598
let minfos =
2583-
AllMethInfosOfTypeInScope ncenv.InfoReader nenv (None,ad) PreferOverrides m typ
2584-
|> List.filter minfoFilter
2585-
|> List.filter (fun minfo -> not(addersAndRemovers|>List.exists (fun ar-> ar = minfo.LogicalName)))
2599+
if completionTargets.ResolveAll then
2600+
let minfos =
2601+
AllMethInfosOfTypeInScope ncenv.InfoReader nenv (None,ad) PreferOverrides m typ
2602+
|> List.filter minfoFilter
2603+
2604+
let addersAndRemovers =
2605+
pinfoItems
2606+
|> List.map (function Item.Event(FSEvent(_,_,addValRef,removeValRef)) -> [addValRef.LogicalName;removeValRef.LogicalName] | _ -> [])
2607+
|> List.concat
2608+
2609+
match addersAndRemovers with
2610+
| [] -> minfos
2611+
| addersAndRemovers ->
2612+
let isNotAdderOrRemover (minfo: MethInfo) = not(addersAndRemovers |> List.exists (fun ar -> ar = minfo.LogicalName))
2613+
List.filter isNotAdderOrRemover minfos
25862614

2615+
else []
25872616
// Partition methods into overload sets
25882617
let rec partitionl (l:MethInfo list) acc =
25892618
match l with
@@ -2592,8 +2621,6 @@ let ResolveCompletionsInType (ncenv: NameResolver) nenv isApplicableMeth m ad st
25922621
let nm = h.LogicalName
25932622
partitionl t (NameMultiMap.add nm h acc)
25942623

2595-
2596-
25972624
// Build the results
25982625
ucinfos @
25992626
List.map Item.RecdField rfinfos @
@@ -2891,7 +2918,7 @@ let rec ResolvePartialLongIdentPrim (ncenv: NameResolver) (nenv: NameResolutionE
28912918

28922919
/// Resolve a (possibly incomplete) long identifier to a set of possible resolutions.
28932920
let ResolvePartialLongIdent ncenv nenv isApplicableMeth m ad plid allowObsolete =
2894-
ResolvePartialLongIdentPrim ncenv nenv isApplicableMeth OpenQualified m ad plid allowObsolete
2921+
ResolvePartialLongIdentPrim ncenv nenv (ResolveCompletionTargets.All isApplicableMeth) OpenQualified m ad plid allowObsolete
28952922

28962923
// REVIEW: has much in common with ResolvePartialLongIdentInModuleOrNamespace - probably they should be united
28972924
let rec ResolvePartialLongIdentInModuleOrNamespaceForRecordFields (ncenv: NameResolver) nenv m ad (modref:ModuleOrNamespaceRef) plid allowObsolete =

src/fsharp/nameres.fsi

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,5 +303,10 @@ val FakeInstantiationGenerator : range -> Typar list -> TType list
303303
/// Resolve a (possibly incomplete) long identifier to a set of possible resolutions.
304304
val ResolvePartialLongIdent : NameResolver -> NameResolutionEnv -> (MethInfo -> TType -> bool) -> range -> AccessorDomain -> string list -> bool -> Item list
305305

306+
[<RequireQualifiedAccess>]
307+
type ResolveCompletionTargets =
308+
| All of (MethInfo -> TType -> bool)
309+
| SettablePropertiesAndFields
310+
306311
/// Resolve a (possibly incomplete) long identifier to a set of possible resolutions, qualified by type.
307-
val ResolveCompletionsInType : NameResolver -> NameResolutionEnv -> (MethInfo -> TType -> bool) -> Range.range -> Infos.AccessorDomain -> bool -> TType -> Item list
312+
val ResolveCompletionsInType : NameResolver -> NameResolutionEnv -> ResolveCompletionTargets -> Range.range -> Infos.AccessorDomain -> bool -> TType -> Item list

src/fsharp/vs/ServiceUntypedParse.fs

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ type internal CompletionContext =
7070
// completing records field
7171
| RecordField of RecordContext
7272
| RangeOperator
73+
| NewObject of pos * HashSet<string>
7374

7475
//----------------------------------------------------------------------------
7576
// Untyped scope
@@ -387,6 +388,9 @@ type internal UntypedParseInfo(parsed:UntypedParseResults) =
387388
scope.ValidateBreakpointLocationImpl(pos)
388389

389390
module internal UntypedParseInfoImpl =
391+
392+
let emptyStringSet = HashSet<string>()
393+
390394
let GetUntypedParseResults (upi : UntypedParseInfo) = upi.Results
391395

392396
let GetRangeOfExprLeftOfDot(line,col,parseTreeOpt) =
@@ -726,13 +730,99 @@ module internal UntypedParseInfoImpl =
726730
Some CompletionContext.Invalid
727731
| _ -> None
728732

733+
let (|Operator|_|) name e =
734+
match e with
735+
| SynExpr.App(ExprAtomicFlag.NonAtomic, false, SynExpr.App(ExprAtomicFlag.NonAtomic, true, SynExpr.Ident(ident), lhs, _), rhs, _)
736+
when ident.idText = name -> Some(lhs, rhs)
737+
| _ -> None
738+
729739
// checks if we are in rhs of the range operator
730740
let isInRhsOfRangeOp (p : AstTraversal.TraversePath) =
731741
match p with
732-
| TS.Expr(SynExpr.App(ExprAtomicFlag.NonAtomic, false, SynExpr.App(ExprAtomicFlag.NonAtomic, true, SynExpr.Ident(ident), _, _), _, _))::_
733-
when ident.idText = "op_Range"-> true
742+
| TS.Expr(Operator "op_Range" _)::_ -> true
734743
| _ -> false
735744

745+
let (|Setter|_|) e =
746+
match e with
747+
| Operator "op_Equality" (SynExpr.Ident id, _) -> Some id
748+
| _ -> None
749+
750+
let findSetters argList =
751+
match argList with
752+
| SynExpr.Paren(SynExpr.Tuple(parameters, _, _), _, _, _) ->
753+
let setters = HashSet()
754+
for p in parameters do
755+
match p with
756+
| Setter id -> ignore(setters.Add id.idText)
757+
| _ -> ()
758+
setters
759+
| _ -> emptyStringSet
760+
761+
let endOfLastIdent (lid: LongIdentWithDots) =
762+
let last = List.last lid.Lid
763+
last.idRange.End
764+
765+
let endOfClosingTokenOrLastIdent (mClosing: range option) (lid : LongIdentWithDots) =
766+
match mClosing with
767+
| Some m -> m.End
768+
| None -> endOfLastIdent lid
769+
770+
let endOfClosingTokenOrIdent (mClosing: range option) (id : Ident) =
771+
match mClosing with
772+
| Some m -> m.End
773+
| None -> id.idRange.End
774+
775+
let (|NewObject|_|) e =
776+
match e with
777+
| (SynExpr.New (_, SynType.LongIdent typeName, arg, _)) ->
778+
// new A()
779+
Some (endOfLastIdent typeName, findSetters arg)
780+
| (SynExpr.New (_, SynType.App(SynType.LongIdent typeName, _, _, _, mGreaterThan, _, _), arg, _)) ->
781+
// new A<_>()
782+
Some (endOfClosingTokenOrLastIdent mGreaterThan typeName, findSetters arg)
783+
| (SynExpr.App (ExprAtomicFlag.Atomic, false, SynExpr.Ident id, arg, _)) ->
784+
// A()
785+
Some (id.idRange.End, findSetters arg)
786+
| (SynExpr.App (ExprAtomicFlag.Atomic, false, SynExpr.TypeApp(SynExpr.Ident id, _, _, _, mGreaterThan, _, _), arg, _)) ->
787+
// A<_>()
788+
Some (endOfClosingTokenOrIdent mGreaterThan id , findSetters arg)
789+
| (SynExpr.App (ExprAtomicFlag.Atomic, false, SynExpr.LongIdent(_, lid, _, _), arg, _)) ->
790+
// A.B()
791+
Some (endOfLastIdent lid, findSetters arg)
792+
| (SynExpr.App (ExprAtomicFlag.Atomic, false, SynExpr.TypeApp(SynExpr.LongIdent(_, lid, _, _), _, _, _, mGreaterThan, _, _), arg, _)) ->
793+
// A.B<_>()
794+
Some (endOfClosingTokenOrLastIdent mGreaterThan lid, findSetters arg)
795+
| _ -> None
796+
797+
let isOnTheRightOfComma (elements: SynExpr list) (commas: range list) current =
798+
let rec loop elements (commas: range list) =
799+
match elements with
800+
| x::xs ->
801+
match commas with
802+
| c::cs ->
803+
if x === current then posLt c.End pos || posEq c.End pos
804+
else loop xs cs
805+
| _ -> false
806+
| _ -> false
807+
loop elements commas
808+
809+
let (|PartOfParameterList|_|) precedingArgument path =
810+
match path with
811+
| TS.Expr(SynExpr.Paren _)::TS.Expr(NewObject(args))::_ ->
812+
if Option.isSome precedingArgument then None else Some args
813+
| TS.Expr(SynExpr.Tuple (elements, commas, _))::TS.Expr(SynExpr.Paren _)::TS.Expr(NewObject(args))::_ ->
814+
match precedingArgument with
815+
| None -> Some args
816+
| Some e ->
817+
// if expression is passed then
818+
// 1. find it in among elements of the tuple
819+
// 2. find corresponding comma
820+
// 3. check that current position is past the comma
821+
// this is used for cases like (a = something-here.) if the cursor is after .
822+
// in this case this is not object initializer completion context
823+
if isOnTheRightOfComma elements commas e then Some args else None
824+
| _ -> None
825+
736826
let walker =
737827
{
738828
new AstTraversal.AstVisitorBase<_>() with
@@ -741,7 +831,26 @@ module internal UntypedParseInfoImpl =
741831
match defaultTraverse expr with
742832
| None -> Some (CompletionContext.RangeOperator) // nothing was found - report that we were in the context of range operator
743833
| x -> x // ok, we found something - return it
744-
else defaultTraverse expr
834+
else
835+
match expr with
836+
// new A($)
837+
| SynExpr.Const(SynConst.Unit, m) when rangeContainsPos m pos ->
838+
match path with
839+
| TS.Expr(NewObject args)::_ -> Some (CompletionContext.NewObject args)
840+
| _ -> defaultTraverse expr
841+
// new (... A$)
842+
| SynExpr.Ident id when id.idRange.End = pos ->
843+
match path with
844+
| PartOfParameterList None args -> Some (CompletionContext.NewObject args)
845+
| _ -> defaultTraverse expr
846+
// new (A$ = 1)
847+
// new (A = 1,$)
848+
| Setter id when id.idRange.End = pos || rangeBeforePos expr.Range pos ->
849+
let precedingArgument = if id.idRange.End = pos then None else Some expr
850+
match path with
851+
| PartOfParameterList precedingArgument args-> Some (CompletionContext.NewObject args)
852+
| _ -> defaultTraverse expr
853+
| _ -> defaultTraverse expr
745854

746855
member this.VisitRecordField(path, copyOpt, field) =
747856
let contextFromTreePath completionPath =

src/fsharp/vs/ServiceUntypedParse.fsi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ type internal CompletionContext =
6767
// completing records field
6868
| RecordField of RecordContext
6969
| RangeOperator
70-
70+
// completing property setters in constructor call
71+
// end of constructor ast node * list of properties that were already set
72+
| NewObject of pos * HashSet<string>
7173

7274
// implementation details used by other code in the compiler
7375
module internal UntypedParseInfoImpl =

0 commit comments

Comments
 (0)