source/analysis/classSummary.ml (1,095 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 Pyre
open Ast
open Statement
module Attribute : sig
type getter_property = {
self: Expression.t option;
return: Expression.t option;
}
[@@deriving compare, sexp, show, hash]
type setter_property = {
self: Expression.t option;
value: Expression.t option;
}
[@@deriving compare, sexp, show, hash]
type property_kind =
| ReadOnly of { getter: getter_property }
| ReadWrite of {
getter: getter_property;
setter: setter_property;
}
[@@deriving compare, sexp, show, hash]
type origin =
| Explicit
| Implicit
[@@deriving compare, sexp, show, hash]
type value_and_origin = {
value: Expression.t;
origin: origin;
}
[@@deriving compare, sexp, show, hash]
type simple = {
annotation: Expression.t option;
values: value_and_origin list;
primitive: bool;
frozen: bool;
toplevel: bool;
implicit: bool;
nested_class: bool;
}
[@@deriving compare, sexp, show, hash]
type method_ = {
signatures: Define.Signature.t list;
static: bool;
final: bool;
}
[@@deriving compare, sexp, show, hash]
type property = {
async: bool;
class_property: bool;
kind: property_kind;
}
[@@deriving compare, sexp, show, hash]
type kind =
| Simple of simple
| Method of method_
| Property of property
[@@deriving compare, sexp, show, hash]
type attribute = {
kind: kind;
name: Identifier.t;
}
[@@deriving compare, sexp, show, hash]
type t = attribute Node.t [@@deriving compare, sexp, show, hash]
val create_simple
: location:Location.t ->
?annotation:Expression.t ->
?value_and_origin:value_and_origin ->
?primitive:bool ->
?frozen:bool ->
?toplevel:bool ->
?implicit:bool ->
?nested_class:bool ->
name:string ->
unit ->
t
val name : parent:Reference.t -> Expression.t -> string option
val location_insensitive_compare : t -> t -> int
val location_insensitive_compare_kind : kind -> kind -> int
end = struct
type getter_property = {
self: Expression.t option;
return: Expression.t option;
}
[@@deriving compare, sexp, show, hash]
type setter_property = {
self: Expression.t option;
value: Expression.t option;
}
[@@deriving compare, sexp, show, hash]
type property_kind =
| ReadOnly of { getter: getter_property }
| ReadWrite of {
getter: getter_property;
setter: setter_property;
}
[@@deriving compare, sexp, show, hash]
type origin =
| Explicit
| Implicit
[@@deriving compare, sexp, show, hash]
type value_and_origin = {
value: Expression.t;
origin: origin;
}
[@@deriving compare, sexp, show, hash]
type simple = {
annotation: Expression.t option;
values: value_and_origin list;
primitive: bool;
frozen: bool;
toplevel: bool;
implicit: bool;
nested_class: bool;
}
[@@deriving compare, sexp, show, hash]
type method_ = {
signatures: Define.Signature.t list;
static: bool;
final: bool;
}
[@@deriving compare, sexp, show, hash]
type property = {
async: bool;
class_property: bool;
kind: property_kind;
}
[@@deriving compare, sexp, show, hash]
type kind =
| Simple of simple
| Method of method_
| Property of property
[@@deriving compare, sexp, show, hash]
type attribute = {
kind: kind;
name: Identifier.t;
}
[@@deriving compare, sexp, show, hash]
type t = attribute Node.t [@@deriving compare, sexp, show, hash]
let location_insensitive_compare_property_kind left right =
match left, right with
| ( ReadOnly { getter = { self = left_self; return = left_return } },
ReadOnly { getter = { self = right_self; return = right_return } } ) -> (
match Option.compare Expression.location_insensitive_compare left_self right_self with
| x when not (Int.equal x 0) -> x
| _ -> Option.compare Expression.location_insensitive_compare left_return right_return)
| ( ReadWrite
{
getter = { self = left_getter_self; return = left_getter_return };
setter = { self = left_setter_self; value = left_setter_value };
},
ReadWrite
{
getter = { self = right_getter_self; return = right_getter_return };
setter = { self = right_setter_self; value = right_setter_value };
} ) -> (
match
Option.compare Expression.location_insensitive_compare left_getter_self right_getter_self
with
| x when not (Int.equal x 0) -> x
| _ -> (
match
Option.compare
Expression.location_insensitive_compare
left_getter_return
right_getter_return
with
| x when not (Int.equal x 0) -> x
| _ -> (
match
Option.compare
Expression.location_insensitive_compare
left_setter_self
right_setter_self
with
| x when not (Int.equal x 0) -> x
| _ ->
Option.compare
Expression.location_insensitive_compare
left_setter_value
right_setter_value)))
| _ -> -1
let location_insensitive_compare_property left right =
match Bool.compare left.async right.async with
| x when not (Int.equal x 0) -> x
| _ -> (
match Bool.compare left.class_property right.class_property with
| x when not (Int.equal x 0) -> x
| _ -> location_insensitive_compare_property_kind left.kind right.kind)
let location_insensitive_compare_method left right =
match compare_method_ { left with signatures = [] } { right with signatures = [] } with
| x when not (Int.equal x 0) -> x
| _ ->
List.compare Define.Signature.location_insensitive_compare left.signatures right.signatures
let location_insensitive_compare_simple left right =
match
compare_simple
{ left with annotation = None; values = [] }
{ right with annotation = None; values = [] }
with
| x when not (Int.equal x 0) -> x
| _ -> (
match
Option.compare Expression.location_insensitive_compare left.annotation right.annotation
with
| x when not (Int.equal x 0) -> x
| _ ->
let compare_expression_and_origin
{ value = left_expression; origin = left_origin }
{ value = right_expression; origin = right_origin }
=
match compare_origin left_origin right_origin with
| 0 -> Expression.location_insensitive_compare left_expression right_expression
| nonzero -> nonzero
in
List.compare compare_expression_and_origin left.values right.values)
let location_insensitive_compare_kind left right =
match left, right with
| Simple left, Simple right -> location_insensitive_compare_simple left right
| Method left, Method right -> location_insensitive_compare_method left right
| Property left, Property right -> location_insensitive_compare_property left right
| _ -> -1
let location_insensitive_compare_attribute left right =
match [%compare: Identifier.t] left.name right.name with
| x when not (Int.equal x 0) -> x
| _ -> location_insensitive_compare_kind left.kind right.kind
let location_insensitive_compare =
Node.location_insensitive_compare location_insensitive_compare_attribute
let create_simple
~location
?annotation
?value_and_origin
?(primitive = false)
?(frozen = false)
?(toplevel = true)
?(implicit = false)
?(nested_class = false)
~name
()
=
let values = Option.to_list value_and_origin in
{
name;
kind = Simple { annotation; values; primitive; frozen; toplevel; implicit; nested_class };
}
|> Node.create ~location
let name ~parent target =
let open Expression in
match Node.value target with
| Expression.Name
(Name.Attribute { base = { Node.value = Expression.Name name; _ }; attribute; _ })
when Option.equal Reference.equal (Some parent) (name_to_reference name) ->
Some attribute
| _ -> None
end
module ClassAttributes = struct
type attribute_map = Attribute.attribute Node.t Identifier.SerializableMap.t
[@@deriving compare, sexp, show, hash]
type t = {
explicitly_assigned_attributes: attribute_map;
constructor_attributes: attribute_map;
test_setup_attributes: attribute_map;
additional_attributes: attribute_map;
}
[@@deriving compare, sexp, show, hash]
let assigned_by_define
({ Define.body; signature = { parameters; _ }; _ } as define)
~definition:{ Class.body = definition_body; _ }
: Attribute.t Identifier.SerializableMap.t
=
let open Expression in
let parameter_annotations =
let add_parameter map = function
| { Node.value = { Parameter.name; annotation = Some annotation; _ }; _ } ->
Identifier.SerializableMap.set map ~key:name ~data:annotation
| _ -> map
in
List.fold ~init:Identifier.SerializableMap.empty ~f:add_parameter parameters
in
let attribute ~toplevel map { Node.value; _ } =
match value with
| Statement.Assign { Assign.target; annotation; value; _ } -> (
let simple_attribute ~map ~target:({ Node.location; _ } as target) ~annotation =
match target with
| {
Node.value =
Expression.Name
(Name.Attribute
{ base = { Node.value = Name (Name.Identifier self); _ }; attribute = name; _ });
_;
}
when Identifier.equal self (Define.self_identifier define) ->
let simple =
{
Attribute.annotation;
values = [{ value; origin = Implicit }];
primitive = true;
frozen = false;
toplevel;
implicit = true;
nested_class = false;
}
|> Node.create ~location
in
let update = function
| Some (head, tail) -> Some (simple, head :: tail)
| None -> Some (simple, [])
in
Identifier.SerializableMap.update name update map
| _ -> map
in
match target with
| { Node.value = Name _; _ } ->
let annotation =
match toplevel, annotation, value with
| true, None, { Node.value = Name (Name.Identifier value); _ } ->
Identifier.SerializableMap.find_opt value parameter_annotations
| _ -> annotation
in
simple_attribute ~map ~target ~annotation
| { Node.value = Tuple targets; _ } ->
List.fold
~init:map
~f:(fun map target -> simple_attribute ~map ~target ~annotation)
targets
| _ -> map)
| _ -> map
in
let merge_attributes name = function
| { Node.location; value = simple }, [] ->
{ Attribute.kind = Simple simple; name } |> Node.create ~location
| ({ Node.location; value = simple } as head), tail ->
let annotation =
let annotation = function
| { Node.value = { Attribute.annotation = Some annotation; _ }; _ } -> Some annotation
| _ -> None
in
match List.filter_map ~f:annotation (head :: tail) with
| [] -> None
| ({ Node.location; _ } as annotation) :: annotations ->
let argument_value =
Node.create_with_default_location (Expression.Tuple (annotation :: annotations))
in
if List.for_all ~f:([%compare.equal: Expression.t] annotation) annotations then
Some annotation
else
Some
{
Node.location;
value =
Call
{
callee =
{
Node.location;
value =
Name
(Name.Attribute
{
base =
{
Node.location;
value =
Name
(Name.Attribute
{
base =
{
Node.location;
value = Name (Name.Identifier "typing");
};
attribute = "Union";
special = false;
});
};
attribute = "__getitem__";
special = true;
});
};
arguments = [{ Call.Argument.name = None; value = argument_value }];
};
}
in
{
Node.location;
value = { Attribute.name; Attribute.kind = Simple { simple with annotation } };
}
in
let rec gather_nested_statements ~toplevel body =
(* Can't use `Visit` module due to circularity :( *)
let expand_statement ({ Node.value; _ } as statement) =
match value with
| Statement.If { If.body; orelse; _ }
| For { For.body; orelse; _ }
| While { While.body; orelse; _ } ->
gather_nested_statements ~toplevel:false body
@ gather_nested_statements ~toplevel:false orelse
| Try { Try.body; orelse; finally; _ } ->
gather_nested_statements ~toplevel:false body
@ gather_nested_statements ~toplevel:false orelse
@ gather_nested_statements ~toplevel:false finally
| With { With.body; _ } -> gather_nested_statements ~toplevel:false body
| Expression
{
Node.value =
Call
{
callee =
{
Node.value =
Name
(Name.Attribute
{
base = { Node.value = Name (Name.Identifier self); _ };
attribute = name;
_;
});
_;
};
_;
};
_;
}
when Identifier.equal self (Define.self_identifier define) ->
(* Look for method in class definition. *)
let inline = function
| {
Node.value =
Statement.Define
{ signature = { name = callee; parent = Some parent; _ }; body; _ };
_;
}
when Reference.equal callee (Reference.create ~prefix:parent name) ->
Some body
| _ -> None
in
List.find_map ~f:inline definition_body |> Option.value ~default:[statement]
| _ ->
if toplevel then
[]
else
[statement]
in
List.concat_map ~f:expand_statement body
in
let toplevel_attributes =
body |> List.fold ~init:Identifier.SerializableMap.empty ~f:(attribute ~toplevel:true)
in
gather_nested_statements ~toplevel:true body
|> List.fold ~init:toplevel_attributes ~f:(attribute ~toplevel:false)
|> Identifier.SerializableMap.mapi merge_attributes
module PropertyDefine = struct
type getter = {
name: string;
self_annotation: Expression.t option;
return_annotation: Expression.t option;
location: Location.t;
async: bool;
is_class_property: bool;
}
type setter = {
name: string;
self_annotation: Expression.t option;
value_annotation: Expression.t option;
}
type t =
| Getter of getter
| Setter of setter
let create
~location
({ Define.signature = { name; return_annotation; parameters; parent; _ }; _ } as define)
=
let inspect_decorators name =
let async = Define.is_async define in
let is_instance_property () =
String.Set.exists Recognized.property_decorators ~f:(Define.has_decorator define)
in
let is_class_property () =
String.Set.exists Recognized.classproperty_decorators ~f:(Define.has_decorator define)
in
let self_annotation =
match parameters with
| { Node.value = { Expression.Parameter.annotation; _ }; _ } :: _ -> annotation
| _ -> None
in
let getter ~is_class_property =
Some
(Getter { name; self_annotation; return_annotation; is_class_property; async; location })
in
if is_instance_property () then
getter ~is_class_property:false
else if is_class_property () then
getter ~is_class_property:true
else
match Define.is_property_setter define, parameters with
| ( true,
_
:: { Node.value = { Expression.Parameter.annotation = value_annotation; _ }; _ } :: _
) ->
Some (Setter { name; self_annotation; value_annotation })
| _ -> None
in
parent
>>= fun parent ->
Attribute.name ~parent (Expression.from_reference ~location name) >>= inspect_decorators
end
(* Bias towards the right (previously occuring map in the `|> merge other_map` flow), but
accumulate values *)
let merge_attribute_maps _ left right =
match left, right with
| Some _, None -> left
| None, Some _ -> right
| ( Some { Node.value = { Attribute.kind = Simple { values = left_values; _ }; _ }; _ },
Some
{
Node.value =
{ Attribute.kind = Simple ({ values = right_values; _ } as simple); _ } as right;
location;
} ) ->
Some
{
Node.value =
{ right with kind = Simple { simple with values = right_values @ left_values } };
location;
}
| _ -> right
let create ({ Class.name = parent_name; body; _ } as definition) =
let explicitly_assigned_attributes =
let assigned_attributes map { Node.location; value } =
let open Expression in
match value with
(* Handle multiple assignments on same line *)
| Statement.Assign
{
Assign.target = { Node.value = Tuple targets; _ };
value = { Node.value = Tuple values; _ };
_;
} ->
let add_attribute map ({ Node.location; _ } as target) value =
Attribute.name ~parent:parent_name target
|> function
| Some name ->
let attribute =
Attribute.create_simple
~location
~name
~value_and_origin:{ value; origin = Explicit }
~primitive:true
()
in
Identifier.SerializableMap.set map ~key:name ~data:attribute
| _ -> map
in
if List.length targets = List.length values then
List.fold2_exn ~init:map ~f:add_attribute targets values
else
map
| Assign { Assign.target = { Node.value = Tuple targets; _ }; value; _ } ->
let add_attribute index map ({ Node.location; _ } as target) =
Attribute.name ~parent:parent_name target
|> function
| Some name ->
let value =
let index =
Node.create ~location (Expression.Constant (Constant.Integer index))
in
match value with
| { Node.value = Call _; _ }
| { Node.value = Name _; _ } ->
Some
{
value with
Node.value =
Expression.Call
{
callee =
{
Node.location;
value =
Name
(Name.Attribute
{
base = value;
attribute = "__getitem__";
special = true;
});
};
arguments = [{ Call.Argument.name = None; value = index }];
};
}
| _ -> None
in
value
>>| (fun value ->
Attribute.create_simple
~location
~name
~value_and_origin:{ value; origin = Explicit }
~primitive:true
())
>>| (fun data -> Identifier.SerializableMap.set map ~key:name ~data)
|> Option.value ~default:map
| _ -> map
in
List.foldi ~init:map ~f:add_attribute targets
| Assign { Assign.target; annotation; value; _ } -> (
Attribute.name ~parent:parent_name target
|> function
| Some name ->
let frozen = Class.is_frozen definition in
let attribute =
Attribute.create_simple
~location
~name
~value_and_origin:{ value; origin = Explicit }
?annotation
~primitive:true
~frozen
()
in
Identifier.SerializableMap.set map ~key:name ~data:attribute
| _ -> map)
| _ -> map
in
List.fold ~init:Identifier.SerializableMap.empty ~f:assigned_attributes body
in
let get_implicits defines =
List.map defines ~f:(assigned_by_define ~definition)
|> List.fold
~init:Identifier.SerializableMap.empty
~f:(Identifier.SerializableMap.merge merge_attribute_maps)
in
let constructor_attributes = Class.constructors ~in_test:false definition |> get_implicits in
let test_setup_attributes =
let test_setups { Class.body; _ } =
let constructor = function
| { Node.value = Statement.Define define; _ } when Define.is_test_setup define ->
Some define
| _ -> None
in
List.filter_map ~f:constructor body
in
test_setups definition |> get_implicits
in
let additional_attributes =
let property_attributes =
let property_attributes map = function
| { Node.location; value = Statement.Define define } -> (
match PropertyDefine.create ~location define with
| Some (Setter { name; _ } as kind)
| Some (Getter { name; _ } as kind) ->
let data =
Identifier.SerializableMap.find_opt name map
|> Option.value ~default:(None, None)
|> fun (existing_getter, existing_setter) ->
match kind with
| Setter setter -> existing_getter, Some setter
| Getter getter -> Some getter, existing_setter
in
Identifier.SerializableMap.set map ~key:name ~data
| None -> map)
| _ -> map
in
let consolidate = function
| _, (None, None)
| _, (None, Some _) ->
None (* not allowed *)
| ( _,
( Some
{
PropertyDefine.name;
self_annotation;
return_annotation;
async;
location;
is_class_property;
},
None ) ) ->
( name,
{
Attribute.name;
kind =
Property
{
kind =
ReadOnly
{ getter = { self = self_annotation; return = return_annotation } };
async;
class_property = is_class_property;
};
}
|> Node.create ~location )
|> Option.some
| ( _,
( Some
{
PropertyDefine.name;
self_annotation = getter_self_annotation;
return_annotation = getter_return_annotation;
async;
location;
is_class_property;
},
Some
{
PropertyDefine.self_annotation = setter_self_annotation;
value_annotation = setter_value_annotation;
_;
} ) ) ->
( name,
{
Attribute.name;
kind =
Property
{
kind =
ReadWrite
{
getter =
{ self = getter_self_annotation; return = getter_return_annotation };
setter =
{ self = setter_self_annotation; value = setter_value_annotation };
};
async;
class_property = is_class_property;
};
}
|> Node.create ~location )
|> Option.some
in
List.fold ~init:Identifier.SerializableMap.empty ~f:property_attributes body
|> Identifier.SerializableMap.to_seq
|> Seq.filter_map consolidate
|> Identifier.SerializableMap.of_seq
in
let callable_attributes =
let callable_attributes map { Node.location; value } =
match value with
| Statement.Define ({ Define.signature = { name = target; _ } as signature; _ } as define)
->
Attribute.name (Expression.from_reference ~location target) ~parent:parent_name
>>| (fun name ->
let attribute =
match Identifier.SerializableMap.find_opt name map with
| Some { Node.value = { Attribute.kind = Method { signatures; _ }; _ }; _ } ->
{
Attribute.name;
kind =
Method
{
signatures = signature :: signatures;
static = Define.is_static_method define;
final = Define.is_final_method define;
};
}
|> Node.create ~location
| _ ->
{
Attribute.name;
kind =
Method
{
signatures = [signature];
static = Define.is_static_method define;
final = Define.is_final_method define;
};
}
|> Node.create ~location
in
Identifier.SerializableMap.set map ~key:name ~data:attribute)
|> Option.value ~default:map
| _ -> map
in
List.fold ~init:Identifier.SerializableMap.empty ~f:callable_attributes body
in
let class_attributes =
let callable_attributes map { Node.location; value } =
match value with
| Statement.Class { name; _ } ->
let open Expression in
let annotation =
let meta_annotation =
{
Node.location;
value =
Expression.Call
{
callee =
{
Node.location;
value =
Name
(Name.Attribute
{
base =
{
Node.location;
value =
Name
(Name.Attribute
{
base =
{
Node.location;
value = Name (Name.Identifier "typing");
};
attribute = "Type";
special = false;
});
};
attribute = "__getitem__";
special = true;
});
};
arguments =
[
{
Call.Argument.name = None;
value = from_reference ~location:Location.any name;
};
];
};
}
in
{
Node.location;
value =
Expression.Call
{
callee =
{
Node.location;
value =
Name
(Name.Attribute
{
base =
{
Node.location;
value =
Name
(Name.Attribute
{
base =
{
Node.location;
value = Name (Name.Identifier "typing");
};
attribute = "ClassVar";
special = false;
});
};
attribute = "__getitem__";
special = true;
});
};
arguments = [{ Call.Argument.name = None; value = meta_annotation }];
};
}
in
let attribute_name = Reference.last name in
Identifier.SerializableMap.set
map
~key:attribute_name
~data:
(Attribute.create_simple
~location
~name:attribute_name
~annotation
~nested_class:true
())
| _ -> map
in
List.fold ~init:Identifier.SerializableMap.empty ~f:callable_attributes body
in
let slots_attributes =
let slots_attributes map { Node.value; _ } =
let open Expression in
let is_slots = function
| Expression.Name (Name.Identifier "__slots__")
| Expression.Name (Name.Attribute { attribute = "__slots__"; _ }) ->
true
| _ -> false
in
match value with
| Statement.Assign
{
Assign.target = { Node.value = target_value; _ };
value = { Node.value = List attributes; location };
_;
}
when is_slots target_value ->
let add_attribute map { Node.value; _ } =
match value with
| Expression.Constant (Constant.String { StringLiteral.value; _ }) ->
Attribute.create_simple ~location ~name:value ()
|> fun attribute ->
Identifier.SerializableMap.set map ~key:value ~data:attribute
| _ -> map
in
List.fold ~init:map ~f:add_attribute attributes
| _ -> map
in
List.fold ~init:Identifier.SerializableMap.empty ~f:slots_attributes body
in
property_attributes
|> Identifier.SerializableMap.merge merge_attribute_maps callable_attributes
|> Identifier.SerializableMap.merge merge_attribute_maps class_attributes
|> Identifier.SerializableMap.merge merge_attribute_maps slots_attributes
in
{
explicitly_assigned_attributes;
constructor_attributes;
test_setup_attributes;
additional_attributes;
}
let empty () =
{
explicitly_assigned_attributes = Identifier.SerializableMap.empty;
constructor_attributes = Identifier.SerializableMap.empty;
test_setup_attributes = Identifier.SerializableMap.empty;
additional_attributes = Identifier.SerializableMap.empty;
}
let implicit_attributes
~in_test
{ constructor_attributes; test_setup_attributes; additional_attributes; _ }
=
let implicitly_assigned_attributes =
if in_test then
Identifier.SerializableMap.merge
merge_attribute_maps
test_setup_attributes
constructor_attributes
else
constructor_attributes
in
(* Merge with decreasing priority. *)
additional_attributes
|> Identifier.SerializableMap.merge merge_attribute_maps implicitly_assigned_attributes
let attributes
~include_generated_attributes
~in_test
({ explicitly_assigned_attributes; _ } as components)
=
if not include_generated_attributes then
explicitly_assigned_attributes
else
explicitly_assigned_attributes
|> Identifier.SerializableMap.merge
merge_attribute_maps
(implicit_attributes ~in_test components)
(* Exposed for testing only *)
module Private = struct
let assigned_by_define = assigned_by_define
end
end
module ClassSummary = struct
type bases = {
base_classes: Expression.t list;
metaclass: Expression.t option;
init_subclass_arguments: Expression.Call.Argument.t list;
}
[@@deriving compare, sexp, show, hash, to_yojson]
type t = {
name: Reference.t;
qualifier: Reference.t;
bases: bases;
decorators: Expression.t list;
class_attributes: ClassAttributes.t;
}
[@@deriving compare, sexp, show, hash]
let create ~qualifier ({ Ast.Statement.Class.name; decorators; _ } as class_definition) =
let bases =
{
base_classes = Ast.Statement.Class.base_classes class_definition;
metaclass = Ast.Statement.Class.metaclass class_definition;
init_subclass_arguments = Ast.Statement.Class.init_subclass_arguments class_definition;
}
in
{
name;
qualifier;
bases;
decorators;
class_attributes = ClassAttributes.create class_definition;
}
let is_protocol { bases = { base_classes; _ }; _ } =
let is_protocol { Node.value; _ } =
let open Expression in
match value with
| Expression.Call
{
callee =
{
Node.value =
Name
(Attribute
{
base =
{
Node.value =
Name
(Attribute
{
base = { Node.value = Name (Identifier typing); _ };
attribute = "Protocol";
_;
});
_;
};
attribute = "__getitem__";
_;
});
_;
};
_;
}
| Name
(Attribute
{ base = { Node.value = Name (Identifier typing); _ }; attribute = "Protocol"; _ })
when String.equal typing "typing" || String.equal typing "typing_extensions" ->
true
| _ -> false
in
List.exists ~f:is_protocol base_classes
let has_decorator { decorators; _ } decorator =
Expression.exists_in_list ~expression_list:decorators decorator
let is_final definition =
has_decorator definition "typing.final" || has_decorator definition "typing_extensions.final"
let is_abstract { bases = { base_classes; metaclass; _ }; _ } =
let open Expression in
let is_abstract_base_class { Node.value; _ } =
match value with
| Expression.Name
(Attribute { base = { Node.value = Name (Identifier "abc"); _ }; attribute = "ABC"; _ })
->
true
| _ -> false
in
let is_abstract_metaclass = function
| Some
{
Node.value =
Expression.Name
(Attribute
{ base = { Node.value = Name (Identifier "abc"); _ }; attribute = "ABCMeta"; _ });
_;
} ->
true
| _ -> false
in
List.exists base_classes ~f:is_abstract_base_class || is_abstract_metaclass metaclass
let fields_tuple_value { class_attributes; _ } =
let attributes =
ClassAttributes.attributes ~include_generated_attributes:false ~in_test:false class_attributes
in
match Identifier.SerializableMap.find_opt "_fields" attributes with
| Some
{
Node.value =
{
kind =
Simple
{ values = [{ origin = Explicit; value = { Node.value = Tuple fields; _ } }]; _ };
_;
};
_;
} ->
let name = function
| {
Node.value =
Ast.Expression.(Expression.Constant (Constant.String { StringLiteral.value; _ }));
_;
} ->
Some value
| _ -> None
in
Some (List.filter_map fields ~f:name)
| _ -> None
let name { name; _ } = name
let bases { bases; _ } = bases
let base_classes { bases = { base_classes; _ }; _ } = base_classes
let constructor_attributes { class_attributes = { ClassAttributes.constructor_attributes; _ }; _ }
=
constructor_attributes
let attributes ?(include_generated_attributes = true) ?(in_test = false) { class_attributes; _ } =
ClassAttributes.attributes ~include_generated_attributes ~in_test class_attributes
end
include ClassSummary