Skip to content

Commit

Permalink
[flow][get-def] Special case get-def for Pick with string literals in…
Browse files Browse the repository at this point in the history
… the second targ

Summary: Changelog: [ide] For `Pick` utility type with string literals in the second type argument, go-to-definition on the string literal will jump to corresponding prop's location

Reviewed By: gkz

Differential Revision: D60403582

fbshipit-source-id: 48bbfc4371a7d163fcd8efdca51b0e07d5488212
  • Loading branch information
SamChou19815 authored and facebook-github-bot committed Jul 30, 2024
1 parent a50bef2 commit 6680c15
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 19 deletions.
34 changes: 23 additions & 11 deletions src/services/get_def/getDef_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,11 @@ let extract_member_def ~loc_of_aloc ~cx ~file_sig ~typed_ast_opt ~force_instance
| Some def_locs -> Ok (Nel.map loc_of_aloc def_locs, Some name)
| None -> Error (Printf.sprintf "failed to find member %s in members map" name)

let rec process_request ~loc_of_aloc ~cx ~is_legit_require ~ast ~typed_ast_opt ~file_sig :
(ALoc.t, ALoc.t * Type.t) Get_def_request.t -> (Loc.t Nel.t * string option, string) result =
let rec process_request ~loc_of_aloc ~cx ~is_legit_require ~ast ~typed_ast_opt ~file_sig ~scope_info
: (ALoc.t, ALoc.t * Type.t) Get_def_request.t -> (Loc.t Nel.t * string option, string) result =
function
| Get_def_request.Identifier { name; loc = (aloc, type_) } ->
let loc = loc_of_aloc aloc in
let scope_info =
Scope_builder.program ~enable_enums:(Context.enable_enums cx) ~with_types:true ast
in
let all_uses = Scope_api.With_Loc.all_uses scope_info in
let matching_uses = Loc_collections.LocSet.filter (fun use -> Loc.contains use loc) all_uses in
(match Loc_collections.LocSet.elements matching_uses with
Expand All @@ -50,7 +47,15 @@ let rec process_request ~loc_of_aloc ~cx ~is_legit_require ~ast ~typed_ast_opt ~
Ok (def_loc, Some name)
| [] ->
let req = Get_def_request.Type { annot = (aloc, type_); name = Some name } in
process_request ~loc_of_aloc ~cx ~is_legit_require ~ast ~typed_ast_opt ~file_sig req
process_request
~loc_of_aloc
~cx
~is_legit_require
~ast
~typed_ast_opt
~file_sig
~scope_info
req
| _ :: _ :: _ -> Error "Scope builder found multiple matching identifiers")
| Get_def_request.(Member { prop_name = name; object_type = (_loc, t); force_instance }) ->
extract_member_def ~loc_of_aloc ~cx ~file_sig ~typed_ast_opt ~force_instance t name
Expand All @@ -70,7 +75,7 @@ let rec process_request ~loc_of_aloc ~cx ~is_legit_require ~ast ~typed_ast_opt ~
Member { prop_name = name; object_type = (loc, props_object); force_instance = false }
)
in
process_request ~loc_of_aloc ~cx ~is_legit_require ~ast ~typed_ast_opt ~file_sig req
process_request ~loc_of_aloc ~cx ~is_legit_require ~ast ~typed_ast_opt ~file_sig ~scope_info req

module Depth = struct
let limit = 100
Expand Down Expand Up @@ -121,11 +126,15 @@ end

let get_def ~loc_of_aloc ~cx ~file_sig ~ast ~available_ast ~purpose requested_loc =
let require_loc_map = File_sig.require_loc_map file_sig in
let scope_info =
Scope_builder.program ~enable_enums:(Context.enable_enums cx) ~with_types:true ast
in
let is_local_use aloc = Scope_api.With_Loc.is_local_use scope_info (loc_of_aloc aloc) in
let is_legit_require source_aloc =
let source_loc = loc_of_aloc source_aloc in
SMap.exists (fun _ locs -> List.exists (fun loc -> loc = source_loc) locs) require_loc_map
in
let rec loop ~depth req_loc loop_name =
let rec loop ~scope_info ~depth req_loc loop_name =
match Depth.add req_loc depth with
| Error error ->
let trace_of_locs locs =
Expand All @@ -148,7 +157,9 @@ let get_def ~loc_of_aloc ~cx ~file_sig ~ast ~available_ast ~purpose requested_lo
| Ok Depth.NoResult ->
let open Get_def_process_location in
let result =
match process_location cx ~is_legit_require ~available_ast ~purpose req_loc with
match
process_location cx ~is_local_use ~is_legit_require ~available_ast ~purpose req_loc
with
| OwnDef (aloc, name) -> Def (LocSet.singleton (loc_of_aloc aloc), Some name)
| Request request -> begin
match
Expand All @@ -159,6 +170,7 @@ let get_def ~loc_of_aloc ~cx ~file_sig ~ast ~available_ast ~purpose requested_lo
~ast
~typed_ast_opt:(Typed_ast_utils.typed_ast_of_available_ast available_ast)
~file_sig
~scope_info
request
with
| Ok (res_locs, name) ->
Expand All @@ -172,7 +184,7 @@ let get_def ~loc_of_aloc ~cx ~file_sig ~ast ~available_ast ~purpose requested_lo
if Loc.equal req_loc res_loc || Loc.(res_loc.source <> requested_loc.source) then
Def (LocSet.singleton res_loc, name)
else
match loop ~depth res_loc name with
match loop ~scope_info ~depth res_loc name with
| Bad_loc _ -> Def (LocSet.singleton res_loc, name)
| Def_error msg -> Partial (LocSet.singleton res_loc, name, msg)
| (Def _ | Partial _) as res -> res
Expand All @@ -198,4 +210,4 @@ let get_def ~loc_of_aloc ~cx ~file_sig ~ast ~available_ast ~purpose requested_lo
Depth.cache_result req_loc result depth;
result
in
loop ~depth:(Depth.empty ()) requested_loc None
loop ~depth:(Depth.empty ()) ~scope_info requested_loc None
54 changes: 46 additions & 8 deletions src/services/get_def/get_def_process_location.ml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ let annot_of_jsx_name =
| MemberExpression (_, MemberExpression.{ property = (annot, _); _ }) ->
annot

class virtual ['T] searcher _cx ~is_legit_require ~covers_target ~purpose =
class virtual ['T] searcher _cx ~is_local_use ~is_legit_require ~covers_target ~purpose =
object (this)
inherit [ALoc.t, 'T, ALoc.t, 'T] enclosing_node_mapper as super

Expand Down Expand Up @@ -238,6 +238,40 @@ class virtual ['T] searcher _cx ~is_legit_require ~covers_target ~purpose =
end;
super#member loc expr

method! generic_type expr =
let open Ast.Type.Generic in
let { id; targs; comments = _ } = expr in
begin
match (id, targs) with
| ( Identifier.Unqualified (pick_annot, { Ast.Identifier.name = "Pick"; comments = _ }),
Some (_, { Ast.Type.TypeArgs.arguments = [(obj_annot, _); keys]; _ })
)
when not (is_local_use (this#loc_of_annot pick_annot)) ->
let request annot prop_name =
if this#annot_covers_target annot then
let obj_annot =
(this#loc_of_annot obj_annot, this#type_from_enclosing_node obj_annot)
in
let result =
Get_def_request.(
Member { prop_name; object_type = obj_annot; force_instance = false }
)
in
this#request result
in
(match keys with
| (annot, Ast.Type.StringLiteral { Ast.StringLiteral.value; _ }) -> request annot value
| (_, Ast.Type.Union { Ast.Type.Union.types = (t1, t2, ts); _ }) ->
Base.List.iter (t1 :: t2 :: ts) ~f:(function
| (annot, Ast.Type.StringLiteral { Ast.StringLiteral.value; _ }) ->
request annot value
| _ -> ()
)
| _ -> ())
| _ -> ()
end;
super#generic_type expr

method! indexed_access_type expr =
let open Ast.Type.IndexedAccess in
let { _object; index; comments = _ } = expr in
Expand Down Expand Up @@ -591,9 +625,9 @@ class virtual ['T] searcher _cx ~is_legit_require ~covers_target ~purpose =
pn
end

class typed_ast_searcher _cx ~typed_ast:_ ~is_legit_require ~covers_target ~purpose =
class typed_ast_searcher _cx ~typed_ast:_ ~is_local_use ~is_legit_require ~covers_target ~purpose =
object
inherit [ALoc.t * Type.t] searcher _cx ~is_legit_require ~covers_target ~purpose
inherit [ALoc.t * Type.t] searcher _cx ~is_local_use ~is_legit_require ~covers_target ~purpose

method private loc_of_annot (loc, _) = loc

Expand Down Expand Up @@ -653,9 +687,9 @@ let find_remote_name_def_loc_in_node loc node =

exception Internal_error_exn of internal_error

class on_demand_searcher cx ~is_legit_require ~covers_target ~purpose =
class on_demand_searcher cx ~is_local_use ~is_legit_require ~covers_target ~purpose =
object (this)
inherit [ALoc.t] searcher cx ~is_legit_require ~covers_target ~purpose
inherit [ALoc.t] searcher cx ~is_local_use ~is_legit_require ~covers_target ~purpose

method on_type_annot x = x

Expand Down Expand Up @@ -713,13 +747,17 @@ let search ~searcher ast =
| exception Internal_error_exn err -> InternalError err
| _ -> searcher#found_loc

let process_location cx ~available_ast ~is_legit_require ~purpose loc =
let process_location cx ~available_ast ~is_local_use ~is_legit_require ~purpose loc =
match available_ast with
| Typed_ast_utils.Typed_ast typed_ast ->
let covers_target test_loc = Reason.in_range loc (ALoc.to_loc_exn test_loc) in
let searcher = new typed_ast_searcher () ~typed_ast ~is_legit_require ~covers_target ~purpose in
let searcher =
new typed_ast_searcher () ~typed_ast ~is_local_use ~is_legit_require ~covers_target ~purpose
in
search ~searcher typed_ast
| Typed_ast_utils.ALoc_ast aloc_ast ->
let covers_target test_loc = Reason.in_range loc (ALoc.to_loc_exn test_loc) in
let searcher = new on_demand_searcher cx ~is_legit_require ~covers_target ~purpose in
let searcher =
new on_demand_searcher cx ~is_local_use ~is_legit_require ~covers_target ~purpose
in
search ~searcher aloc_ast
2 changes: 2 additions & 0 deletions src/services/get_def/get_def_process_location.mli
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type 'loc result =
* `ALoc.t * Type.t` through an inferred constraint. *)
class virtual ['T] searcher :
'c
-> is_local_use:(ALoc.t -> bool)
-> is_legit_require:(ALoc.t -> bool)
-> covers_target:(ALoc.t -> bool)
-> purpose:Get_def_types.Purpose.t
Expand Down Expand Up @@ -62,6 +63,7 @@ val process_type_request : Context.t -> Type.t -> (ALoc.t, string) Stdlib.result
val process_location :
Context.t ->
available_ast:Typed_ast_utils.available_ast ->
is_local_use:(ALoc.t -> bool) ->
is_legit_require:(ALoc.t -> bool) ->
purpose:Get_def_types.Purpose.t ->
Loc.t ->
Expand Down
27 changes: 27 additions & 0 deletions tests/get_def2/get_def2.exp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,33 @@ constructor.js:14:6
Flags:
constructor.js:10:7,10:9

pick.js:8:24
Flags:

pick.js:12:23
Flags:
pick.js:3:13,3:15

pick.js:14:23
Flags:
pick.js:3:26,3:28

pick.js:17:23
Flags:
pick.js:16:6,16:8

pick.js:20:23
Flags:
pick.js:3:13,3:15

pick.js:22:32
Flags:
pick.js:3:26,3:28

pick.js:24:29
Flags:
pick.js:3:26,3:28

utilities.js:7:10
Flags:
utilities.js:3:20,3:22
Expand Down
25 changes: 25 additions & 0 deletions tests/get_def2/pick.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// @flow

type Obj = {foo: string, bar: number};

function locally_defined_pick() {
type Pick<A, B> = A;

type T = Pick<Obj, 'foo'>; // no result for locally defined pick
// ^
}

type T1 = Pick<Obj, 'foo'>; // jump to foo prop
// ^
type T2 = Pick<Obj, 'bar'>; // jump to bar prop
// ^
type Foo = 'foo';
type T3 = Pick<Obj, Foo>; // jump to type Foo, instead of jumping to foo prop
// ^

type T4 = Pick<Obj, 'foo' | 'bar'>; // jump to foo prop
// ^
type T5 = Pick<Obj, 'foo' | 'bar'>; // jump to bar prop
// ^
type T6 = Pick<Obj, Foo | 'bar'>; // jump to bar prop
// ^
1 change: 1 addition & 0 deletions tests/get_def2/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ queries_in_file get-def "declare.js"
queries_in_file get-def "private_name.js"
queries_in_file get-def "static.js"
queries_in_file get-def "constructor.js"
queries_in_file get-def "pick.js"
queries_in_file get-def "utilities.js"

0 comments on commit 6680c15

Please sign in to comment.