source/analysis/aliasEnvironment.ml (400 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 Pyre
open Expression
module PreviousEnvironment = EmptyStubEnvironment
let preprocess_alias_value value = Preprocessing.expand_strings_in_annotation_expression value
module AliasValue = struct
type t = Type.alias option
let prefix = Prefix.make ()
let description = "Alias"
let unmarshall value = Marshal.from_string value 0
let compare = Option.compare Type.compare_alias
end
module UnresolvedAlias = struct
type t = {
value: Expression.t;
target: Reference.t;
}
let unchecked_resolve ~unparsed ~target map =
match
Type.create
~aliases:(fun ?replace_unbound_parameters_with_any:_ name -> Map.find map name)
unparsed
with
| Type.Variable variable ->
if Type.Variable.Unary.contains_subvariable variable then
Type.Any
else
Type.Variable { variable with variable = Reference.show target }
| annotation -> annotation
type check_result =
| Resolved of Type.alias
| HasDependents of {
unparsed: Expression.t;
dependencies: string list;
}
let checked_resolve empty_stub_environment { value; target } ~dependency () =
let value_annotation = unchecked_resolve ~unparsed:value ~target String.Map.empty in
let dependencies = String.Hash_set.create () in
let module TrackedTransform = Type.Transform.Make (struct
type state = unit
let visit_children_before _ = function
| Type.NoneType -> false
| _ -> true
let visit_children_after = false
let visit _ annotation =
let new_state, transformed_annotation =
match annotation with
| Type.Parametric { name = primitive; _ }
| Primitive primitive ->
let reference =
match Node.value (Type.expression (Type.Primitive primitive)) with
| Expression.Name name when is_simple_name name -> name_to_reference_exn name
| _ -> Reference.create "typing.Any"
in
if
EmptyStubEnvironment.ReadOnly.from_empty_stub
empty_stub_environment
?dependency
reference
then
(), Type.Any
else if
UnannotatedGlobalEnvironment.ReadOnly.class_exists
?dependency
(EmptyStubEnvironment.ReadOnly.unannotated_global_environment
empty_stub_environment)
primitive
|| UnannotatedGlobalEnvironment.ReadOnly.module_exists
(EmptyStubEnvironment.ReadOnly.unannotated_global_environment
empty_stub_environment)
?dependency
(Reference.create primitive)
(* Don't consider "..." in `MyCallable[..., int]` to be a dependent alias. *)
|| Identifier.equal primitive "..."
then
(), annotation
else
let _ = Hash_set.add dependencies primitive in
(), annotation
| _ -> (), annotation
in
{ Type.Transform.transformed_annotation; new_state }
end)
in
let _, annotation = TrackedTransform.visit () value_annotation in
if Hash_set.is_empty dependencies then
Resolved (Type.TypeAlias annotation)
else
HasDependents { unparsed = value; dependencies = Hash_set.to_list dependencies }
end
type extracted =
| VariableAlias of Type.Variable.t
| TypeAlias of UnresolvedAlias.t
let extract_alias unannotated_global_environment name ~dependency =
let extract_alias = function
| UnannotatedGlobal.SimpleAssign { explicit_annotation; value; _ } -> (
let target_annotation =
Type.create ~aliases:Type.empty_aliases (from_reference ~location:Location.any name)
in
match Node.value value, explicit_annotation with
| ( _,
Some
{
Node.value =
Call
{
callee =
{
Node.value =
Name
(Name.Attribute
{
base =
{
Node.value =
Name
(Name.Attribute
{
base =
{ Node.value = Name (Name.Identifier "typing"); _ };
attribute = "Type";
_;
});
_;
};
attribute = "__getitem__";
_;
});
_;
};
arguments =
[
{
Call.Argument.value =
{
Node.value =
Call
{
callee =
{
Node.value =
Name
(Name.Attribute
{
base =
{
Node.value =
Name
(Name.Attribute
{
base =
{
Node.value =
Name
(Name.Identifier
"mypy_extensions");
_;
};
attribute = "TypedDict";
_;
});
_;
};
attribute = "__getitem__";
_;
});
_;
};
_;
};
_;
};
_;
};
];
};
_;
} ) ->
if not (Type.is_top target_annotation) then
let value = delocalize value in
Some (TypeAlias { target = name; value })
else
None
| ( (Call _ | Name _ | Constant (Constant.String _)),
Some
{
Node.value =
Name
(Name.Attribute
{
base = { Node.value = Name (Name.Identifier "typing_extensions"); _ };
attribute = "TypeAlias";
_;
});
_;
} )
| (Call _ | Name _), None -> (
match Type.Variable.parse_declaration (delocalize value) ~target:name with
| Some variable -> Some (VariableAlias variable)
| _ ->
let value = preprocess_alias_value value |> delocalize in
let value_annotation = Type.create ~aliases:Type.empty_aliases value in
if
not
(Type.contains_unknown target_annotation
|| Type.contains_unknown value_annotation
|| Type.equal value_annotation target_annotation)
then
Some (TypeAlias { target = name; value })
else
None)
| _ -> None)
| UnannotatedGlobal.Imported import -> (
if
UnannotatedGlobalEnvironment.ReadOnly.class_exists
unannotated_global_environment
(Reference.show name)
then
None
else
let original_name = UnannotatedGlobal.ImportEntry.deprecated_original_name import in
match Reference.as_list name, Reference.as_list original_name with
| [single_identifier], [typing; identifier]
when String.equal typing "typing" && String.equal single_identifier identifier ->
(* builtins has a bare qualifier. Don't export bare aliases from typing. *)
None
| _ ->
let value = from_reference ~location:Location.any original_name in
Some (TypeAlias { target = name; value }))
| TupleAssign _
| Class
| Define _ ->
None
in
UnannotatedGlobalEnvironment.ReadOnly.get_unannotated_global
?dependency
unannotated_global_environment
name
>>= extract_alias
let produce_alias empty_stub_environment global_name ~dependency =
let maybe_convert_to_recursive_alias alias_reference = function
| Type.TypeAlias annotation
when (not (Identifier.equal (Reference.show alias_reference) "typing_extensions"))
&& Type.RecursiveType.is_recursive_alias_reference
~alias_name:(Reference.show alias_reference)
annotation ->
let alias_name = Reference.show alias_reference in
let is_directly_recursive =
Type.resolve_class annotation
>>| List.exists ~f:(fun { Type.class_name; _ } -> Identifier.equal class_name alias_name)
|> Option.value ~default:true
in
let is_generic = Type.contains_variable annotation in
Type.TypeAlias (Type.RecursiveType.create ~name:alias_name ~body:annotation)
|> Option.some_if ((not is_directly_recursive) && not is_generic)
| resolved_alias -> Some resolved_alias
in
(* TODO(T53786399): Optimize this function. Theres a lot of perf potentially to be gained here,
currently biasing towards simplicity *)
let rec get_aliased_type_for current ~visited =
(* This means we're in a loop *)
if Set.mem visited current then
None
else
let visited = Set.add visited current in
let resolve_after_resolving_dependencies = function
| VariableAlias variable -> Some (Type.VariableAlias variable)
| TypeAlias unresolved -> (
match
UnresolvedAlias.checked_resolve empty_stub_environment unresolved ~dependency ()
with
| Resolved alias -> Some alias
| HasDependents { unparsed; dependencies } ->
let solve_pair dependency =
get_aliased_type_for (Reference.create dependency) ~visited
>>| fun solution -> dependency, solution
in
List.filter dependencies ~f:(fun dependency ->
not (Reference.equal (Reference.create dependency) current))
|> List.map ~f:solve_pair
|> Option.all
>>| String.Map.of_alist_exn
>>| UnresolvedAlias.unchecked_resolve ~target:current ~unparsed
>>| fun alias -> Type.TypeAlias alias)
in
extract_alias
(EmptyStubEnvironment.ReadOnly.unannotated_global_environment empty_stub_environment)
current
~dependency
>>= resolve_after_resolving_dependencies
>>= maybe_convert_to_recursive_alias current
in
if Reference.equal global_name (Reference.create "typing.NoReturn") then
(* TODO(T76821797): We should fix upstream `typeshed` instead of doing special-case like this. *)
None
else
get_aliased_type_for global_name ~visited:Reference.Set.empty
module Aliases = Environment.EnvironmentTable.NoCache (struct
module PreviousEnvironment = PreviousEnvironment
module Key = SharedMemoryKeys.StringKey
module Value = AliasValue
type trigger = Reference.t [@@deriving sexp, compare]
let convert_trigger = Reference.show
let key_to_trigger = Reference.create ?prefix:None
module TriggerSet = Reference.Set
let lazy_incremental = false
let produce_value = produce_alias
let filter_upstream_dependency = function
| SharedMemoryKeys.AliasRegister name -> Some name
| _ -> None
let trigger_to_dependency name = SharedMemoryKeys.AliasRegister name
let legacy_invalidated_keys upstream_update =
UnannotatedGlobalEnvironment.UpdateResult.previous_unannotated_globals upstream_update
let all_keys unannotated_global_environment =
UnannotatedGlobalEnvironment.ReadOnly.all_unannotated_globals unannotated_global_environment
|> List.map ~f:Reference.show
let serialize_value = function
| Some alias -> Type.show_alias alias
| None -> "None"
let show_key = Fn.id
let equal_value = Option.equal Type.equal_alias
end)
include Aliases
module ReadOnly = struct
include Aliases.ReadOnly
let get_alias environment ?dependency ?replace_unbound_parameters_with_any:_ name =
get environment ?dependency name
let empty_stub_environment = upstream_environment
let unannotated_global_environment read_only =
empty_stub_environment read_only |> EmptyStubEnvironment.ReadOnly.unannotated_global_environment
let parse_annotation_without_validating_type_parameters
environment
?modify_aliases
?dependency
?(allow_untracked = false)
expression
=
let modify_aliases =
Option.value modify_aliases ~default:(fun ?replace_unbound_parameters_with_any:_ name -> name)
in
let parsed =
let expression = preprocess_alias_value expression |> delocalize in
let aliases ?replace_unbound_parameters_with_any name =
get_alias environment ?dependency name
>>| modify_aliases ?replace_unbound_parameters_with_any
in
Type.create ~aliases expression
in
let annotation =
let constraints = function
| Type.Primitive name ->
let originates_from_empty_stub =
let reference = Reference.create name in
EmptyStubEnvironment.ReadOnly.from_empty_stub
?dependency
(empty_stub_environment environment)
reference
in
if originates_from_empty_stub then
Some Type.Any
else
None
| _ -> None
in
Type.instantiate parsed ~constraints
in
let contains_untracked =
UnannotatedGlobalEnvironment.ReadOnly.contains_untracked
(unannotated_global_environment environment)
?dependency
in
if contains_untracked annotation && not allow_untracked then
Type.Top
else
annotation
let parse_as_parameter_specification_instance_annotation
environment
?dependency
~variable_parameter_annotation
~keywords_parameter_annotation
=
let variable_parameter_annotation, keywords_parameter_annotation =
delocalize variable_parameter_annotation, delocalize keywords_parameter_annotation
in
Type.Variable.Variadic.Parameters.parse_instance_annotation
~create_type:Type.create
~aliases:(fun ?replace_unbound_parameters_with_any:_ name ->
get_alias environment ?dependency name)
~variable_parameter_annotation
~keywords_parameter_annotation
end
module AliasReadOnly = ReadOnly
module UpdateResult = Aliases.UpdateResult