source/interprocedural/target.ml (232 lines of code) (raw):
(*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open Core
open Ast
open Statement
open Analysis
open Pyre
type method_name = {
class_name: string;
method_name: string;
}
[@@deriving show, sexp, compare, hash, eq]
type function_t = [ `Function of string ] [@@deriving show, sexp, compare, hash, eq]
type method_t = [ `Method of method_name ] [@@deriving show, sexp, compare, hash, eq]
type callable_t =
[ function_t
| method_t
]
[@@deriving show, sexp, compare, hash, eq]
type override_t = [ `OverrideTarget of method_name ] [@@deriving show, sexp, compare, hash, eq]
(* Technically not a callable, but we store models of some fields/globals, E.g. os.environ, or
HttpRequest.GET *)
type object_t = [ `Object of string ] [@@deriving show, sexp, compare, hash, eq]
type non_override_t =
[ callable_t
| object_t
]
[@@deriving show, sexp, compare, hash, eq]
type t =
[ non_override_t
| override_t
]
[@@deriving show, sexp, hash, eq]
(* Lower priority appears earlier in comparison. *)
let priority = function
| `Function _ -> 0
| `Method _ -> 1
| `OverrideTarget _ -> 2
| `Object _ -> 3
let compare left right =
let priority_comparison = Int.compare (priority left) (priority right) in
if priority_comparison <> 0 then
priority_comparison
else (* left and right must have the same variant. *)
match left, right with
| `Function first, `Function second -> String.compare first second
| `Method first, `Method second -> compare_method_name first second
| `OverrideTarget first, `OverrideTarget second -> compare_method_name first second
| `Object first, `Object second -> String.compare first second
| _ -> failwith "The compared targets must belong to the same variant."
(* pp forces type to be equal to t, but we want [<t] *)
let pretty_print formatter callable =
let callable = (callable :> t) in
match callable with
| `Function name -> String.pp formatter name
| `Method { class_name; method_name } ->
String.pp formatter (Format.sprintf "%s::%s" class_name method_name)
| `OverrideTarget { class_name; method_name } ->
String.pp formatter (Format.sprintf "Override{%s::%s}" class_name method_name)
| `Object name -> String.pp formatter (Format.sprintf "Object{%s}" name)
type t_with_result = callable_t
let create_function reference = `Function (Reference.show reference)
let create_method_name ?(suffix = "") reference =
{
class_name = Reference.prefix reference >>| Reference.show |> Option.value ~default:"";
method_name = Reference.last reference ^ suffix;
}
let create_method reference = `Method (create_method_name reference)
let create_property_setter reference = `Method (create_method_name ~suffix:"$setter" reference)
let create_override reference = `OverrideTarget (create_method_name reference)
let create_property_setter_override reference =
`OverrideTarget (create_method_name ~suffix:"$setter" reference)
let create { Node.value = define; _ } =
let open Define in
let name = define.signature.name in
match define.signature.parent with
| Some _ ->
if Define.is_property_setter define then
create_property_setter name
else
create_method name
| None -> create_function name
let create_derived_override override ~at_type =
match override with
| `OverrideTarget { method_name; _ } ->
`OverrideTarget { class_name = Reference.show at_type; method_name }
let create_object reference = `Object (Reference.show reference)
module Key = struct
type nonrec t = t
let sexp_of_t = sexp_of_t
let t_of_sexp = t_of_sexp
let hash = hash
let hash_fold_t = hash_fold_t
let to_string = show
let compare = compare
type out = string
let from_string = ident
end
module CallableKey = struct
type t = callable_t [@@deriving sexp, hash]
let to_string = show_callable_t
let compare = compare_callable_t
type out = string
let from_string = ident
end
module OverrideKey = struct
type t = override_t [@@deriving sexp, hash]
let to_string = show_override_t
let compare = compare_override_t
type out = string
let from_string = ident
end
module Map = Core.Map.Make (Key)
module Set = Caml.Set.Make (Key)
module CallableMap = Core.Map.Make (CallableKey)
module CallableSet = Caml.Set.Make (CallableKey)
module CallableHashSet = Core.Hash_set.Make (CallableKey)
module OverrideSet = Caml.Set.Make (OverrideKey)
module Hashable = Core.Hashable.Make (Key)
module HashMap = Hashable.Table
module HashSet = Hashable.Hash_set
let get_module_and_definition ~resolution callable =
let get_bodies { class_name; method_name } =
let method_name, is_setter =
if String.is_suffix method_name ~suffix:"$setter" then
String.drop_suffix method_name (String.length "$setter"), true
else
method_name, false
in
let define_name =
Reference.combine (Reference.create class_name) (Reference.create method_name)
in
GlobalResolution.function_definition resolution define_name
>>| fun ({ FunctionDefinition.qualifier; _ } as definitions) ->
FunctionDefinition.all_bodies definitions |> fun bodies -> qualifier, bodies, is_setter
in
match callable with
| `Function name ->
Reference.create name
|> GlobalResolution.function_definition resolution
>>= fun ({ FunctionDefinition.qualifier; _ } as definitions) ->
FunctionDefinition.all_bodies definitions
|> List.find ~f:(fun { Node.value; _ } -> not (Define.is_overloaded_function value))
>>= fun body -> Some (qualifier, body)
| `Method method_name -> (
match get_bodies method_name with
| Some (qualifier, bodies, is_setter) ->
if is_setter then
let is_setter { Node.value = { Define.signature; _ }; _ } =
Define.Signature.is_property_setter signature
in
List.find bodies ~f:is_setter >>| fun body -> qualifier, body
else
List.hd bodies >>| fun body -> qualifier, body
| None -> None)
let resolve_method ~resolution ~class_type ~method_name =
let callable_implementation =
Type.split class_type
|> fst
|> Type.primitive_name
>>= GlobalResolution.attribute_from_class_name
~transitive:true
~resolution
~name:method_name
~instantiated:class_type
in
match callable_implementation with
| Some callable when Annotated.Attribute.defined callable ->
Annotated.Attribute.annotation callable
|> Annotation.annotation
|> Type.callable_name
>>| create_method
| _ -> None
let get_override_reference = function
| `OverrideTarget { class_name; method_name } ->
Reference.combine (Reference.create class_name) (Reference.create method_name)
let get_method_reference = function
| `Method { class_name; method_name } ->
Reference.combine (Reference.create class_name) (Reference.create method_name)
let get_corresponding_method = function
| `OverrideTarget method_name -> `Method method_name
let get_corresponding_override = function
| `Method method_name -> `OverrideTarget method_name
let get_callable_t = function
| `Function name -> Some (`Function name)
| `Method method_name -> Some (`Method method_name)
| `OverrideTarget method_name -> Some (`Method method_name)
| `Object _ -> None
let show = function
| `Function target -> Format.sprintf "%s (fun)" target
| `Method { class_name; method_name } -> Format.sprintf "%s::%s (method)" class_name method_name
| `OverrideTarget { class_name; method_name } ->
Format.sprintf "%s::%s (override)" class_name method_name
| `Object name -> Format.sprintf "%s (object)" name
let external_target_name = function
| `Function target -> target |> Reference.create |> Reference.delocalize |> Reference.show
| `Method { class_name; method_name } -> Format.sprintf "%s.%s" class_name method_name
| `OverrideTarget { class_name; method_name } ->
Format.sprintf "Ovr{%s::%s}" class_name method_name
| `Object name -> Format.sprintf "Obj{%s}" name
let class_name = function
| `Method { class_name; _ } -> Some class_name
| `OverrideTarget { class_name; _ } -> Some class_name
| `Function _
| `Object _ ->
None
let method_name = function
| `Method { method_name; _ } -> Some method_name
| `OverrideTarget { method_name; _ } -> Some method_name
| `Function _
| `Object _ ->
None
let get_short_name = function
| `Function target -> target
| `Method { method_name; _ }
| `OverrideTarget { method_name; _ } ->
method_name
| `Object name -> name
let override_to_method = function
| `Function target -> `Function target
| `Method target
| `OverrideTarget target ->
`Method target
| `Object name -> `Object name
let compare target1 target2 =
let target1 = (target1 :> t) in
let target2 = (target2 :> t) in
compare target1 target2