source/analysis/unannotatedGlobalEnvironment.ml (932 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
open Expression
open SharedMemoryKeys
module ResolvedReference = struct
type export =
| FromModuleGetattr
| Exported of Module.Export.Name.t
[@@deriving sexp, compare, hash]
type t =
| Module of Reference.t
| ModuleAttribute of {
from: Reference.t;
name: Identifier.t;
export: export;
remaining: Identifier.t list;
}
| PlaceholderStub of {
stub_module: Reference.t;
remaining: Identifier.t list;
}
[@@deriving sexp, compare, hash]
end
module ReadOnly = struct
type t = {
ast_environment: AstEnvironment.ReadOnly.t;
class_exists: ?dependency:DependencyKey.registered -> string -> bool;
all_classes: unit -> Type.Primitive.t list;
all_indices: unit -> IndexTracker.t list;
all_unannotated_globals: unit -> Reference.t list;
all_defines: unit -> Reference.t list;
all_defines_in_module: Reference.t -> Reference.t list;
get_class_definition:
?dependency:DependencyKey.registered -> string -> ClassSummary.t Node.t option;
get_unannotated_global:
?dependency:DependencyKey.registered -> Reference.t -> UnannotatedGlobal.t option;
get_define: ?dependency:DependencyKey.registered -> Reference.t -> FunctionDefinition.t option;
get_define_body: ?dependency:DependencyKey.registered -> Reference.t -> Define.t Node.t option;
get_module_metadata: ?dependency:DependencyKey.registered -> Reference.t -> Module.t option;
module_exists: ?dependency:SharedMemoryKeys.DependencyKey.registered -> Reference.t -> bool;
}
let ast_environment { ast_environment; _ } = ast_environment
let unannotated_global_environment = Fn.id
let class_exists { class_exists; _ } = class_exists
let all_classes { all_classes; _ } = all_classes ()
let all_indices { all_indices; _ } = all_indices ()
let all_defines { all_defines; _ } = all_defines ()
let all_unannotated_globals { all_unannotated_globals; _ } = all_unannotated_globals ()
let get_class_definition { get_class_definition; _ } = get_class_definition
let get_unannotated_global { get_unannotated_global; _ } = get_unannotated_global
let get_define { get_define; _ } = get_define
let get_define_body { get_define_body; _ } = get_define_body
let all_defines_in_module { all_defines_in_module; _ } = all_defines_in_module
let primitive_name annotation =
let primitive, _ = Type.split annotation in
Type.primitive_name primitive
let is_protocol { get_class_definition; _ } ?dependency annotation =
primitive_name annotation
>>= get_class_definition ?dependency
>>| Node.value
>>| ClassSummary.is_protocol
|> Option.value ~default:false
let contains_untracked read_only ?dependency annotation =
let is_tracked = class_exists read_only ?dependency in
List.exists ~f:(fun annotation -> not (is_tracked annotation)) (Type.elements annotation)
let get_module_metadata { get_module_metadata; _ } = get_module_metadata
let module_exists { module_exists; _ } = module_exists
let legacy_resolve_exports read_only ?dependency reference =
(* Resolve exports. Fixpoint is necessary due to export/module name conflicts: P59503092 *)
let widening_threshold = 25 in
let rec resolve_exports_fixpoint ~reference ~visited ~count =
if Set.mem visited reference || count > widening_threshold then
reference
else
let rec resolve_exports ~lead ~tail =
match tail with
| head :: tail ->
let incremented_lead = lead @ [head] in
if
Option.is_some
(get_module_metadata
?dependency
read_only
(Reference.create_from_list incremented_lead))
then
resolve_exports ~lead:incremented_lead ~tail
else
get_module_metadata ?dependency read_only (Reference.create_from_list lead)
>>| (fun definition ->
match Module.legacy_aliased_export definition (Reference.create head) with
| Some export -> Reference.combine export (Reference.create_from_list tail)
| _ -> resolve_exports ~lead:(lead @ [head]) ~tail)
|> Option.value ~default:reference
| _ -> reference
in
match Reference.as_list reference with
| head :: tail ->
let exported_reference = resolve_exports ~lead:[head] ~tail in
if Reference.is_strict_prefix ~prefix:reference exported_reference then
reference
else
resolve_exports_fixpoint
~reference:exported_reference
~visited:(Set.add visited reference)
~count:(count + 1)
| _ -> reference
in
resolve_exports_fixpoint ~reference ~visited:Reference.Set.empty ~count:0
module ResolveExportItem = struct
module T = struct
type t = {
current_module: Reference.t;
name: Identifier.t;
}
[@@deriving sexp, compare, hash]
end
include T
include Hashable.Make (T)
end
let resolve_exports read_only ?dependency ?(from = Reference.empty) reference =
let visited_set = ResolveExportItem.Hash_set.create () in
let rec resolve_module_alias ~current_module ~names_to_resolve () =
match get_module_metadata ?dependency read_only current_module with
| None ->
let rec resolve_placeholder_stub sofar = function
| [] -> None
| name :: prefixes -> (
let checked_module = List.rev prefixes |> Reference.create_from_list in
let sofar = name :: sofar in
match get_module_metadata ?dependency read_only checked_module with
| Some module_metadata when Module.empty_stub module_metadata ->
Some
(ResolvedReference.PlaceholderStub
{ stub_module = checked_module; remaining = sofar })
| _ -> resolve_placeholder_stub sofar prefixes)
in
(* Make sure none of the parent of `current_module` is placeholder stub *)
resolve_placeholder_stub names_to_resolve (Reference.as_list current_module |> List.rev)
| Some module_metadata -> (
match Module.empty_stub module_metadata with
| true ->
Some
(ResolvedReference.PlaceholderStub
{ stub_module = current_module; remaining = names_to_resolve })
| false -> (
match names_to_resolve with
| [] -> Some (ResolvedReference.Module current_module)
| next_name :: rest_names -> (
let item = { ResolveExportItem.current_module; name = next_name } in
match Hash_set.strict_add visited_set item with
| Result.Error _ ->
(* Module alias cycle detected. Abort resolution. *)
None
| Result.Ok _ -> (
match Module.get_export module_metadata next_name with
| None -> (
match Module.get_export module_metadata "__getattr__" with
| Some Module.Export.(Name (Define { is_getattr_any = true })) ->
Some
(ResolvedReference.ModuleAttribute
{
from = current_module;
name = next_name;
export = ResolvedReference.FromModuleGetattr;
remaining = rest_names;
})
| _ ->
(* We could be hitting an implicit module, or we could be hitting an
explicit module whose name is a prefix of another explicit module.
Keep moving through the current reference chain to make sure we
don't mis-handle those cases. *)
resolve_module_alias
~current_module:
(Reference.create next_name |> Reference.combine current_module)
~names_to_resolve:rest_names
())
| Some (Module.Export.NameAlias { from; name }) ->
if Reference.equal current_module from then
(* This could legitimately happen when an __init__ module trying to
import its sibling modules *)
resolve_module_alias
~current_module:(Reference.create name |> Reference.combine from)
~names_to_resolve:rest_names
()
else
(* We don't know if `name` refers to a module or not. Move forward on
the alias chain. *)
resolve_module_alias
~current_module:from
~names_to_resolve:(name :: rest_names)
()
| Some (Module.Export.Module name) ->
(* `name` is definitely a module. *)
resolve_module_alias ~current_module:name ~names_to_resolve:rest_names ()
| Some (Module.Export.Name export) ->
(* We find a non-module. *)
Some
(ResolvedReference.ModuleAttribute
{
from = current_module;
name = next_name;
export = ResolvedReference.Exported export;
remaining = rest_names;
})))))
in
resolve_module_alias ~current_module:from ~names_to_resolve:(Reference.as_list reference) ()
let first_matching_class_decorator
read_only
?dependency
~names
{ Node.value = { ClassSummary.decorators; _ }; _ }
=
let resolve_and_check_for_match decorator =
match Decorator.from_expression decorator with
| None -> None
| Some ({ Ast.Statement.Decorator.name = { Node.value = name; location }; _ } as decorator) ->
let resolved_name =
match resolve_exports read_only ?dependency name with
| Some (ResolvedReference.ModuleAttribute { from; name; remaining; _ }) ->
Reference.create_from_list (name :: remaining) |> Reference.combine from
| _ -> name
in
let with_matched_name_if_matches name_to_match =
if String.equal (Reference.show resolved_name) name_to_match then
Some { decorator with name = { Node.value = resolved_name; location } }
else
None
in
List.find_map names ~f:with_matched_name_if_matches
in
List.find_map decorators ~f:resolve_and_check_for_match
let exists_matching_class_decorator read_only ?dependency ~names class_summary =
first_matching_class_decorator read_only ?dependency ~names class_summary |> Option.is_some
end
(* The key tracking is necessary because there is no empirical way to determine which classes exist
for a given class. This "fan-out" necessitates internal tracking. However, this module need not
be sealed to ensure write only-ness since we're not dependency tracking this, since it's only
used internally for doing our dependency analysis, and for all_classes, which is only used for
all-or-nothing operations like validating the class hierarchy and printing it for debugging
purposes *)
module KeyTracker = struct
module ClassKeyValue = struct
type t = Identifier.t list [@@deriving compare]
let prefix = Prefix.make ()
let description = "Class keys"
let unmarshall value = Marshal.from_string value 0
end
module UnannotatedGlobalKeyValue = struct
type t = Reference.t list [@@deriving compare]
let prefix = Prefix.make ()
let description = "Class keys"
let unmarshall value = Marshal.from_string value 0
end
module FunctionKeyValue = struct
type t = Reference.t list [@@deriving compare]
let prefix = Prefix.make ()
let description = "TypeCheckUnit keys"
let unmarshall value = Marshal.from_string value 0
end
module ClassKeys = Memory.WithCache.Make (SharedMemoryKeys.ReferenceKey) (ClassKeyValue)
module UnannotatedGlobalKeys =
Memory.WithCache.Make (SharedMemoryKeys.ReferenceKey) (UnannotatedGlobalKeyValue)
module FunctionKeys = Memory.WithCache.Make (SharedMemoryKeys.ReferenceKey) (FunctionKeyValue)
let get_keys keys =
ClassKeys.KeySet.of_list keys
|> ClassKeys.get_batch
|> ClassKeys.KeyMap.values
|> List.filter_map ~f:Fn.id
|> List.concat
let get_unannotated_global_keys keys =
UnannotatedGlobalKeys.KeySet.of_list keys
|> UnannotatedGlobalKeys.get_batch
|> UnannotatedGlobalKeys.KeyMap.values
|> List.filter_map ~f:Fn.id
|> List.concat
let get_define_body_keys keys =
FunctionKeys.KeySet.of_list keys
|> FunctionKeys.get_batch
|> FunctionKeys.KeyMap.values
|> List.filter_map ~f:Fn.id
|> List.concat
end
(* We want to ensure that we are only writing to this table in this phase, not creating internal
dependencies with self-reads. Accordingly read_only should only be called by downstream clients *)
module WriteOnly : sig
val set_class_definition : name:string -> definition:ClassSummary.t Node.t -> unit
val add_to_transaction
: DependencyKey.Transaction.t ->
previous_classes_list:string list ->
previous_unannotated_globals_list:Reference.t list ->
previous_defines_list:Reference.t list ->
previous_modules_list:Reference.t list ->
DependencyKey.Transaction.t
val get_all_dependents
: class_additions:string list ->
unannotated_global_additions:Reference.t list ->
define_additions:Reference.t list ->
DependencyKey.RegisteredSet.t
val direct_data_purge
: previous_classes_list:Type.Primitive.t list ->
previous_unannotated_globals_list:Reference.t list ->
previous_defines_list:Reference.t list ->
previous_modules_list:Reference.t list ->
unit
val set_unannotated_global : target:Reference.t -> UnannotatedGlobal.t -> unit
val set_define : name:Reference.t -> FunctionDefinition.t -> unit
val set_module_metadata : qualifier:Reference.t -> Module.t -> unit
val read_only : ast_environment:AstEnvironment.ReadOnly.t -> ReadOnly.t
end = struct
module ClassSummaryValue = struct
type t = ClassSummary.t Node.t
let prefix = Prefix.make ()
let description = "ClassSummary"
let unmarshall value = Marshal.from_string value 0
let compare = Node.compare ClassSummary.compare
end
module ClassSummaries =
DependencyTrackedMemory.DependencyTrackedTableWithCache
(SharedMemoryKeys.StringKey)
(DependencyKey)
(ClassSummaryValue)
module UnannotatedGlobalValue = struct
type t = UnannotatedGlobal.t
let prefix = Prefix.make ()
let description = "UnannotatedGlobal"
let unmarshall value = Marshal.from_string value 0
let compare = UnannotatedGlobal.compare
end
module UnannotatedGlobals =
DependencyTrackedMemory.DependencyTrackedTableNoCache
(SharedMemoryKeys.ReferenceKey)
(DependencyKey)
(UnannotatedGlobalValue)
module FunctionDefinitionValue = struct
type t = FunctionDefinition.t
let description = "FunctionDefinition"
let prefix = Prefix.make ()
let unmarshall value = Marshal.from_string value 0
let compare = FunctionDefinition.compare
end
module FunctionDefinitions =
DependencyTrackedMemory.DependencyTrackedTableWithCache
(SharedMemoryKeys.ReferenceKey)
(DependencyKey)
(FunctionDefinitionValue)
module ModuleValue = struct
type t = Module.t
let prefix = Prefix.make ()
let description = "Module"
let unmarshall value = Marshal.from_string value 0
let compare = Module.compare
end
module Modules =
DependencyTrackedMemory.DependencyTrackedTableWithCache
(SharedMemoryKeys.ReferenceKey)
(SharedMemoryKeys.DependencyKey)
(ModuleValue)
let set_unannotated_global ~target = UnannotatedGlobals.add target
let set_class_definition ~name ~definition = ClassSummaries.write_through name definition
let set_define ~name definitions = FunctionDefinitions.write_through name definitions
let set_module_metadata ~qualifier = Modules.add qualifier
let add_to_transaction
transaction
~previous_classes_list
~previous_unannotated_globals_list
~previous_defines_list
~previous_modules_list
=
let class_keys = ClassSummaries.KeySet.of_list previous_classes_list in
let unannotated_globals_keys =
UnannotatedGlobals.KeySet.of_list previous_unannotated_globals_list
in
let defines_keys = FunctionDefinitions.KeySet.of_list previous_defines_list in
let module_keys = Modules.KeySet.of_list previous_modules_list in
ClassSummaries.add_to_transaction ~keys:class_keys transaction
|> UnannotatedGlobals.add_to_transaction ~keys:unannotated_globals_keys
|> FunctionDefinitions.add_to_transaction ~keys:defines_keys
|> Modules.add_to_transaction ~keys:module_keys
let get_all_dependents ~class_additions ~unannotated_global_additions ~define_additions =
let function_and_class_dependents =
DependencyKey.RegisteredSet.union
(ClassSummaries.KeySet.of_list class_additions |> ClassSummaries.get_all_dependents)
(FunctionDefinitions.KeySet.of_list define_additions
|> FunctionDefinitions.get_all_dependents)
in
DependencyKey.RegisteredSet.union
function_and_class_dependents
(UnannotatedGlobals.KeySet.of_list unannotated_global_additions
|> UnannotatedGlobals.get_all_dependents)
let direct_data_purge
~previous_classes_list
~previous_unannotated_globals_list
~previous_defines_list
~previous_modules_list
=
ClassSummaries.KeySet.of_list previous_classes_list |> ClassSummaries.remove_batch;
UnannotatedGlobals.KeySet.of_list previous_unannotated_globals_list
|> UnannotatedGlobals.remove_batch;
FunctionDefinitions.KeySet.of_list previous_defines_list |> FunctionDefinitions.remove_batch;
Modules.KeySet.of_list previous_modules_list |> Modules.remove_batch
let read_only ~ast_environment =
let all_classes () =
AstEnvironment.ReadOnly.all_explicit_modules ast_environment |> KeyTracker.get_keys
in
let all_indices () =
all_classes ()
|> Type.Primitive.Set.of_list
|> IndexTracker.indices
|> IndexTracker.Set.to_list
in
let all_unannotated_globals () =
AstEnvironment.ReadOnly.all_explicit_modules ast_environment
|> KeyTracker.get_unannotated_global_keys
in
let all_defines () =
AstEnvironment.ReadOnly.all_explicit_modules ast_environment
|> KeyTracker.get_define_body_keys
in
let class_exists ?dependency name = ClassSummaries.mem ?dependency name in
let get_define = FunctionDefinitions.get in
let get_define_body ?dependency key =
FunctionDefinitions.get ?dependency key >>= fun { FunctionDefinition.body; _ } -> body
in
let all_defines_in_module qualifier = KeyTracker.get_define_body_keys [qualifier] in
let get_module_metadata ?dependency qualifier =
let qualifier =
match Reference.as_list qualifier with
| ["future"; "builtins"]
| ["builtins"] ->
Reference.empty
| _ -> qualifier
in
match Modules.get ?dependency qualifier with
| Some _ as result -> result
| None -> (
match AstEnvironment.ReadOnly.is_module_tracked ast_environment qualifier with
| true -> Some (Module.create_implicit ())
| false -> None)
in
let module_exists ?dependency qualifier =
let qualifier =
match Reference.as_list qualifier with
| ["future"; "builtins"]
| ["builtins"] ->
Reference.empty
| _ -> qualifier
in
match Modules.mem ?dependency qualifier with
| true -> true
| false -> AstEnvironment.ReadOnly.is_module_tracked ast_environment qualifier
in
{
ast_environment;
ReadOnly.get_class_definition = ClassSummaries.get;
all_classes;
all_indices;
all_defines;
class_exists;
get_unannotated_global = UnannotatedGlobals.get;
get_define;
get_define_body;
all_defines_in_module;
all_unannotated_globals;
get_module_metadata;
module_exists;
}
end
let missing_builtin_classes, missing_typing_classes, missing_typing_extensions_classes =
let make ?(bases = []) ?(metaclasses = []) ?(body = []) name =
let create_base annotation =
{ Call.Argument.name = None; value = Type.expression annotation }
in
let create_metaclass annotation =
{
Call.Argument.name = Some (Node.create_with_default_location "metaclass");
value = Type.expression annotation;
}
in
{
Class.name = Reference.create name;
base_arguments = List.map bases ~f:create_base @ List.map metaclasses ~f:create_metaclass;
body;
decorators = [];
top_level_unbound_names = [];
}
|> Node.create_with_default_location
in
let single_unary_generic =
[Type.parametric "typing.Generic" [Single (Variable (Type.Variable.Unary.create "typing._T"))]]
in
let catch_all_generic = [Type.parametric "typing.Generic" [Single Any]] in
let callable_body =
[
Statement.Assign
{
target =
Node.create_with_default_location
(Expression.Name
(Ast.Expression.create_name ~location:Location.any "typing.Callable.__call__"));
annotation = Some (Type.expression Type.object_primitive);
value = Node.create_with_default_location (Expression.Constant Constant.NoneLiteral);
};
]
|> List.map ~f:Node.create_with_default_location
in
let make_dunder_get ~parent ~host ~host_type ~return =
let parent = Reference.create parent in
Statement.Define
{
signature =
{
name = Reference.combine parent (Reference.create "__get__");
parameters =
[
Node.create_with_default_location
{ Ast.Expression.Parameter.name = "self"; value = None; annotation = None };
Node.create_with_default_location
{
Ast.Expression.Parameter.name = "host";
value = None;
annotation = Some (Type.expression host);
};
Node.create_with_default_location
{
Ast.Expression.Parameter.name = "host_type";
value =
Some
(Node.create_with_default_location
(Expression.Constant Constant.NoneLiteral));
annotation = Some (Type.expression host_type);
};
];
decorators = [];
return_annotation = Some (Type.expression return);
async = false;
generator = false;
parent = Some parent;
nesting_define = None;
};
captures = [];
unbound_names = [];
body = [];
}
in
let classmethod_body =
(*
* _T = TypeVar("_T")
* _S = TypeVar("_S")
* class ClassMethod(Generic[_T]):
* def __get__(self, host: object, host_type: _S = None) -> BoundMethod[_T, _S]: ...
*)
[
make_dunder_get
~parent:"typing.ClassMethod"
~host:Type.object_primitive
~host_type:(Variable (Type.Variable.Unary.create "typing._S"))
~return:
(Type.parametric
"BoundMethod"
[
Single (Variable (Type.Variable.Unary.create "typing._T"));
Single (Variable (Type.Variable.Unary.create "typing._S"));
]);
]
|> List.map ~f:Node.create_with_default_location
in
let staticmethod_body =
(*
* _T = TypeVar("_T")
* class StaticMethod(Generic[_T]):
* def __get__(self, host: object, host_type: object = None) -> _T: ...
*)
[
make_dunder_get
~parent:"typing.StaticMethod"
~host:Type.object_primitive
~host_type:Type.object_primitive
~return:(Variable (Type.Variable.Unary.create "typing._T"));
]
|> List.map ~f:Node.create_with_default_location
in
let generic_meta_body =
[
Statement.Define
{
signature =
{
name = Reference.create "typing.GenericMeta.__getitem__";
parameters =
[
{ Parameter.name = "cls"; value = None; annotation = None }
|> Node.create_with_default_location;
{ Parameter.name = "arg"; value = None; annotation = None }
|> Node.create_with_default_location;
];
decorators = [];
return_annotation = None;
async = false;
generator = false;
parent = Some (Reference.create "typing.GenericMeta");
nesting_define = None;
};
captures = [];
unbound_names = [];
body = [];
}
|> Node.create_with_default_location;
]
in
let typing_classes =
[
make "typing.Optional" ~bases:single_unary_generic;
make "typing.NoReturn";
make "typing.Annotated" ~bases:catch_all_generic;
make "typing.Protocol" ~bases:catch_all_generic;
make "typing.Callable" ~bases:catch_all_generic ~body:callable_body;
make "typing.FrozenSet" ~bases:single_unary_generic;
make "typing.ClassVar" ~bases:single_unary_generic;
make "typing.Final" ~bases:catch_all_generic;
make "typing.Literal" ~bases:catch_all_generic;
make "typing.Union" ~bases:catch_all_generic;
make ~metaclasses:[Primitive "typing.GenericMeta"] "typing.Generic";
make "typing.ClassMethod" ~bases:single_unary_generic ~body:classmethod_body;
make "typing.StaticMethod" ~bases:single_unary_generic ~body:staticmethod_body;
make "typing.GenericMeta" ~bases:[Primitive "type"] ~body:generic_meta_body;
make "typing.TypeGuard" ~bases:(Type.bool :: single_unary_generic);
]
in
let typing_extension_classes =
[
make "typing_extensions.Final";
make "typing_extensions.Literal" ~bases:catch_all_generic;
make "typing_extensions.Annotated" ~bases:catch_all_generic;
make "typing_extensions.TypeAlias";
make "typing_extensions.TypeGuard" ~bases:(Type.bool :: single_unary_generic);
]
in
let builtin_classes =
let t_self_expression =
Expression.Name (Name.Identifier "TSelf") |> Node.create_with_default_location
in
[
make
~bases:[Type.parametric "typing.Mapping" [Single Type.string; Single Type.object_primitive]]
~body:(Type.TypedDictionary.defines ~t_self_expression ~total:true)
(Type.TypedDictionary.class_name ~total:true);
make
~bases:[Type.parametric "typing.Mapping" [Single Type.string; Single Type.object_primitive]]
~body:(Type.TypedDictionary.defines ~t_self_expression ~total:false)
(Type.TypedDictionary.class_name ~total:false);
(* I think this may be actually covariant, covariant, but I don't think there's any value in
going out on that limb yet *)
make
~bases:
[
Type.parametric
"typing.Generic"
[Single (Type.variable "typing._T"); Single (Type.variable "typing._S")];
Type.Primitive "typing.Callable";
]
"BoundMethod";
]
in
builtin_classes, typing_classes, typing_extension_classes
let register_class_definitions ({ Source.source_path = { SourcePath.qualifier; _ }; _ } as source) =
(* TODO (T57944324): Support checking classes that are nested inside function bodies *)
let module ClassCollector = Visit.MakeStatementVisitor (struct
type t = Class.t Node.t list
let visit_children _ = true
let statement _ sofar = function
| { Node.location; value = Statement.Class definition } ->
{ Node.location; value = definition } :: sofar
| _ -> sofar
end)
in
let classes = ClassCollector.visit [] source in
let classes =
match Reference.as_list qualifier with
| [] -> classes @ missing_builtin_classes
| ["typing"] -> classes @ missing_typing_classes
| ["typing_extensions"] -> classes @ missing_typing_extensions_classes
| _ -> classes
in
let register new_annotations { Node.location; value = { Class.name; _ } as definition } =
let primitive = Reference.show name in
let definition =
match primitive with
| "type" ->
let value =
Type.expression (Type.parametric "typing.Generic" [Single (Type.variable "typing._T")])
in
{ definition with Class.base_arguments = [{ name = None; value }] }
| _ -> definition
in
WriteOnly.set_class_definition
~name:primitive
~definition:{ Node.location; value = ClassSummary.create ~qualifier definition };
Set.add new_annotations primitive
in
List.fold classes ~init:Type.Primitive.Set.empty ~f:register
|> Set.to_list
|> KeyTracker.ClassKeys.add qualifier
let missing_builtin_globals =
let assign name annotation =
{
UnannotatedGlobal.Collector.Result.name;
unannotated_global =
UnannotatedGlobal.SimpleAssign
{
explicit_annotation = Some (Type.expression annotation);
target_location = Location.WithModule.any;
value = Node.create_with_default_location (Expression.Constant Constant.Ellipsis);
};
}
in
[assign "..." Type.Any; assign "__debug__" Type.bool]
let collect_unannotated_globals ({ Source.source_path = { SourcePath.qualifier; _ }; _ } as source) =
let write { UnannotatedGlobal.Collector.Result.name; unannotated_global } =
let target = Reference.create name |> Reference.combine qualifier in
WriteOnly.set_unannotated_global ~target unannotated_global;
target
in
let merge_defines unannotated_globals_alist =
let not_defines, defines =
List.partition_map unannotated_globals_alist ~f:(function
| { UnannotatedGlobal.Collector.Result.name; unannotated_global = Define defines } ->
Either.Second (name, defines)
| x -> Either.First x)
in
let add_to_map sofar (name, defines) =
let merge_with_existing to_merge = function
| None -> Some to_merge
| Some existing -> Some (to_merge @ existing)
in
Map.change sofar name ~f:(merge_with_existing defines)
in
List.fold defines ~f:add_to_map ~init:Identifier.Map.empty
|> Identifier.Map.to_alist
|> List.map ~f:(fun (name, defines) ->
{
UnannotatedGlobal.Collector.Result.name;
unannotated_global = Define (List.rev defines);
})
|> fun defines -> List.append defines not_defines
in
let drop_classes unannotated_globals =
let is_not_class = function
| { UnannotatedGlobal.Collector.Result.unannotated_global = Class; _ } -> false
| _ -> true
in
List.filter unannotated_globals ~f:is_not_class
in
let globals = UnannotatedGlobal.Collector.from_source source |> merge_defines |> drop_classes in
let globals =
match Reference.as_list qualifier with
| [] -> globals @ missing_builtin_globals
| _ -> globals
in
globals |> List.map ~f:write |> KeyTracker.UnannotatedGlobalKeys.add qualifier
let collect_defines ({ Source.source_path = { SourcePath.qualifier; is_external; _ }; _ } as source)
=
match is_external with
| true ->
(* Do not collect function bodies for external sources as they won't get type checked *)
()
| false ->
let definitions = FunctionDefinition.collect_defines source in
List.iter definitions ~f:(fun (name, definition) -> WriteOnly.set_define ~name definition);
KeyTracker.FunctionKeys.add
qualifier
(List.map definitions ~f:fst |> List.sort ~compare:Reference.compare)
module UpdateResult = struct
type t = {
previous_classes: Type.Primitive.Set.t;
previous_unannotated_globals: Reference.Set.t;
previous_defines: Reference.Set.t;
define_additions: Reference.Set.t;
triggered_dependencies: DependencyKey.RegisteredSet.t;
upstream: AstEnvironment.UpdateResult.t;
read_only: ReadOnly.t;
}
type read_only = ReadOnly.t
let previous_unannotated_globals { previous_unannotated_globals; _ } =
previous_unannotated_globals
let previous_defines { previous_defines; _ } = previous_defines
let previous_classes { previous_classes; _ } = previous_classes
let define_additions { define_additions; _ } = define_additions
let locally_triggered_dependencies { triggered_dependencies; _ } = triggered_dependencies
let upstream { upstream; _ } = upstream
let all_triggered_dependencies { triggered_dependencies; upstream; _ } =
[triggered_dependencies; AstEnvironment.UpdateResult.triggered_dependencies upstream]
let unannotated_global_environment_update_result = Fn.id
let ast_environment_update_result = upstream
let read_only { read_only; _ } = read_only
end
type t = { ast_environment: AstEnvironment.t }
let create ast_environment = { ast_environment }
let ast_environment { ast_environment } = ast_environment
let read_only { ast_environment } =
let ast_environment = AstEnvironment.read_only ast_environment in
WriteOnly.read_only ~ast_environment
let update_this_and_all_preceding_environments { ast_environment } ~scheduler ~configuration trigger
=
let upstream = AstEnvironment.update ~configuration ~scheduler ast_environment trigger in
let ast_environment = AstEnvironment.read_only ast_environment in
let map sources =
let register qualifier =
AstEnvironment.ReadOnly.get_processed_source ~track_dependency:true ast_environment qualifier
>>| (fun source ->
WriteOnly.set_module_metadata ~qualifier (Module.create source);
register_class_definitions source;
collect_unannotated_globals source;
collect_defines source)
|> Option.value ~default:()
in
List.iter sources ~f:register
in
let modified_qualifiers = AstEnvironment.UpdateResult.invalidated_modules upstream in
let update () =
SharedMemoryKeys.DependencyKey.Registry.collected_iter
scheduler
~policy:
(Scheduler.Policy.fixed_chunk_count
~minimum_chunks_per_worker:1
~minimum_chunk_size:100
~preferred_chunks_per_worker:5
())
~f:map
~inputs:modified_qualifiers
in
let previous_classes_list = KeyTracker.get_keys modified_qualifiers in
let previous_classes = Type.Primitive.Set.of_list previous_classes_list in
let previous_unannotated_globals_list =
KeyTracker.get_unannotated_global_keys modified_qualifiers
in
let previous_unannotated_globals = Reference.Set.of_list previous_unannotated_globals_list in
let previous_defines_list = KeyTracker.get_define_body_keys modified_qualifiers in
let previous_defines = Reference.Set.of_list previous_defines_list in
KeyTracker.ClassKeys.KeySet.of_list modified_qualifiers |> KeyTracker.ClassKeys.remove_batch;
KeyTracker.UnannotatedGlobalKeys.KeySet.of_list modified_qualifiers
|> KeyTracker.UnannotatedGlobalKeys.remove_batch;
KeyTracker.FunctionKeys.KeySet.of_list modified_qualifiers |> KeyTracker.FunctionKeys.remove_batch;
match configuration with
| { incremental_style = FineGrained; _ } ->
let define_additions, triggered_dependencies =
Profiling.track_duration_and_shared_memory_with_dynamic_tags
"TableUpdate(Unannotated globals)"
~f:(fun _ ->
let (), mutation_triggers =
DependencyKey.Transaction.empty ~scheduler ~configuration
|> WriteOnly.add_to_transaction
~previous_classes_list
~previous_unannotated_globals_list
~previous_defines_list
~previous_modules_list:modified_qualifiers
|> DependencyKey.Transaction.execute ~update
in
let current_classes =
KeyTracker.get_keys modified_qualifiers |> Type.Primitive.Set.of_list
in
let current_unannotated_globals =
KeyTracker.get_unannotated_global_keys modified_qualifiers |> Reference.Set.of_list
in
let current_defines =
KeyTracker.get_define_body_keys modified_qualifiers |> Reference.Set.of_list
in
let class_additions = Type.Primitive.Set.diff current_classes previous_classes in
let unannotated_global_additions =
Reference.Set.diff current_unannotated_globals previous_unannotated_globals
in
let define_additions = Reference.Set.diff current_defines previous_defines in
let addition_triggers =
WriteOnly.get_all_dependents
~class_additions:(Set.to_list class_additions)
~unannotated_global_additions:(Set.to_list unannotated_global_additions)
~define_additions:(Set.to_list define_additions)
in
let triggered_dependencies =
DependencyKey.RegisteredSet.union addition_triggers mutation_triggers
in
let tags () =
let triggered_dependencies_size =
SharedMemoryKeys.DependencyKey.RegisteredSet.cardinal triggered_dependencies
|> Format.sprintf "%d"
in
[
"phase_name", "Global discovery";
"number_of_triggered_dependencies", triggered_dependencies_size;
]
in
{ Profiling.result = define_additions, triggered_dependencies; tags })
in
{
UpdateResult.previous_classes;
previous_unannotated_globals;
previous_defines;
define_additions;
triggered_dependencies;
upstream;
read_only = WriteOnly.read_only ~ast_environment;
}
| _ ->
let triggered_dependencies =
Profiling.track_duration_and_shared_memory
"LegacyTableUpdate(Unannotated globals)"
~tags:["phase_name", "global discovery"]
~f:(fun _ ->
WriteOnly.direct_data_purge
~previous_classes_list
~previous_unannotated_globals_list
~previous_defines_list
~previous_modules_list:modified_qualifiers;
update ();
DependencyKey.RegisteredSet.empty)
in
{
previous_classes;
previous_unannotated_globals;
previous_defines;
define_additions = Reference.Set.empty;
triggered_dependencies;
upstream;
read_only = WriteOnly.read_only ~ast_environment;
}