source/analysis/globalResolution.ml (495 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 type t = { dependency: SharedMemoryKeys.DependencyKey.registered option; annotated_global_environment: AnnotatedGlobalEnvironment.ReadOnly.t; } let create ?dependency annotated_global_environment = { annotated_global_environment; dependency } let annotated_global_environment { annotated_global_environment; _ } = annotated_global_environment let attribute_resolution resolution = annotated_global_environment resolution |> AnnotatedGlobalEnvironment.ReadOnly.attribute_resolution let class_metadata_environment resolution = annotated_global_environment resolution |> AnnotatedGlobalEnvironment.ReadOnly.class_metadata_environment let class_hierarchy_environment resolution = class_metadata_environment resolution |> ClassMetadataEnvironment.ReadOnly.class_hierarchy_environment let alias_environment resolution = ClassHierarchyEnvironment.ReadOnly.alias_environment (class_hierarchy_environment resolution) let empty_stub_environment resolution = alias_environment resolution |> AliasEnvironment.ReadOnly.empty_stub_environment let unannotated_global_environment resolution = alias_environment resolution |> AliasEnvironment.ReadOnly.unannotated_global_environment let ast_environment resolution = unannotated_global_environment resolution |> UnannotatedGlobalEnvironment.ReadOnly.ast_environment let class_hierarchy ({ dependency; _ } as resolution) = ClassHierarchyEnvironment.ReadOnly.class_hierarchy ?dependency (class_hierarchy_environment resolution) let is_tracked resolution = ClassHierarchy.contains (class_hierarchy resolution) let contains_untracked resolution annotation = List.exists ~f:(fun annotation -> not (is_tracked resolution annotation)) (Type.elements annotation) let is_protocol ({ dependency; _ } as resolution) annotation = UnannotatedGlobalEnvironment.ReadOnly.is_protocol (unannotated_global_environment resolution) ?dependency annotation let primitive_name annotation = let primitive, _ = Type.split annotation in Type.primitive_name primitive let class_definition ({ dependency; _ } as resolution) annotation = primitive_name annotation >>= UnannotatedGlobalEnvironment.ReadOnly.get_class_definition (unannotated_global_environment resolution) ?dependency let define_body ({ dependency; _ } as resolution) = UnannotatedGlobalEnvironment.ReadOnly.get_define_body ?dependency (unannotated_global_environment resolution) let function_definition ({ dependency; _ } as resolution) = UnannotatedGlobalEnvironment.ReadOnly.get_define ?dependency (unannotated_global_environment resolution) let class_metadata ({ dependency; _ } as resolution) annotation = primitive_name annotation >>= ClassMetadataEnvironment.ReadOnly.get_class_metadata ?dependency (class_metadata_environment resolution) let is_suppressed_module ({ dependency; _ } as resolution) reference = EmptyStubEnvironment.ReadOnly.from_empty_stub ?dependency (empty_stub_environment resolution) reference let aliases ({ dependency; _ } as resolution) = AliasEnvironment.ReadOnly.get_alias ?dependency (alias_environment resolution) let base_is_from_placeholder_stub resolution = AnnotatedBases.base_is_from_placeholder_stub ~aliases:(aliases resolution) ~from_empty_stub:(is_suppressed_module resolution) let module_exists ({ dependency; _ } as resolution) = UnannotatedGlobalEnvironment.ReadOnly.module_exists ?dependency (unannotated_global_environment resolution) let get_module_metadata ({ dependency; _ } as resolution) = UnannotatedGlobalEnvironment.ReadOnly.get_module_metadata ?dependency (unannotated_global_environment resolution) let function_definitions ({ dependency; _ } as resolution) reference = let unannotated_global_environment = unannotated_global_environment resolution in UnannotatedGlobalEnvironment.ReadOnly.get_define unannotated_global_environment reference ?dependency >>| FunctionDefinition.all_bodies let full_order ({ dependency; _ } as resolution) = AttributeResolution.ReadOnly.full_order ?dependency (attribute_resolution resolution) let less_or_equal resolution = full_order resolution |> TypeOrder.always_less_or_equal let join resolution = full_order resolution |> TypeOrder.join let meet resolution = full_order resolution |> TypeOrder.meet let widen resolution = full_order resolution |> TypeOrder.widen let types_are_orderable resolution type0 type1 = less_or_equal resolution ~left:type0 ~right:type1 || less_or_equal resolution ~left:type1 ~right:type0 let is_compatible_with resolution = full_order resolution |> TypeOrder.is_compatible_with let is_instantiated resolution = ClassHierarchy.is_instantiated (class_hierarchy resolution) let parse_reference ?(allow_untracked = false) ({ dependency; _ } as resolution) reference = let validation = if allow_untracked then SharedMemoryKeys.ParseAnnotationKey.NoValidation else ValidatePrimitives in Expression.from_reference ~location:Location.any reference |> AttributeResolution.ReadOnly.parse_annotation ?dependency ~validation (attribute_resolution resolution) let is_invariance_mismatch resolution ~left ~right = match left, right with | ( Type.Parametric { name = left_name; parameters = left_parameters }, Type.Parametric { name = right_name; parameters = right_parameters } ) when Identifier.equal left_name right_name -> let zipped = let variances = ClassHierarchy.variables (class_hierarchy resolution) left_name (* TODO(T47346673): Do this check when list variadics have variance *) >>= Type.Variable.all_unary >>| List.map ~f:(fun { Type.Variable.Unary.variance; _ } -> variance) in match variances with | Some variances -> ( match List.zip left_parameters right_parameters with | Ok zipped -> ( match List.zip zipped variances with | Ok zipped -> List.map zipped ~f:(fun ((left, right), variance) -> variance, left, right) |> Option.some | _ -> None) | _ -> None) | _ -> None in let due_to_invariant_variable (variance, left, right) = match variance, left, right with | Type.Variable.Invariant, Type.Parameter.Single left, Type.Parameter.Single right -> less_or_equal resolution ~left ~right | _ -> false in zipped >>| List.exists ~f:due_to_invariant_variable |> Option.value ~default:false | _ -> false let global ({ dependency; _ } as resolution) reference = (* TODO (T41143153): We might want to properly support this by unifying attribute lookup logic for module and for class *) match Reference.last reference with | "__doc__" | "__file__" | "__name__" | "__package__" -> let annotation = Annotation.create_immutable Type.string in Some { AttributeResolution.Global.annotation; undecorated_signature = None; problem = None } | "__path__" -> let annotation = Type.list Type.string |> Annotation.create_immutable in Some { AttributeResolution.Global.annotation; undecorated_signature = None; problem = None } | "__dict__" -> let annotation = Type.dictionary ~key:Type.string ~value:Type.Any |> Annotation.create_immutable in Some { annotation; undecorated_signature = None; problem = None } | _ -> AttributeResolution.ReadOnly.get_global (attribute_resolution resolution) ?dependency reference let attribute_from_class_name ~resolution:({ dependency; _ } as resolution) ?(transitive = false) ?(accessed_through_class = false) ?(special_method = false) class_name ~name ~instantiated = let access = function | Some attribute -> Some attribute | None -> ( match UnannotatedGlobalEnvironment.ReadOnly.get_class_definition (unannotated_global_environment resolution) ?dependency class_name with | Some _ -> AnnotatedAttribute.create ~annotation:Type.Top ~original_annotation:Type.Top ~uninstantiated_annotation:(Some Type.Top) ~abstract:false ~async_property:false ~class_variable:false ~defined:false ~initialized:NotInitialized ~name ~parent:class_name ~visibility:ReadWrite ~property:false ~undecorated_signature:None ~problem:None |> Option.some | None -> None) in AttributeResolution.ReadOnly.attribute ~instantiated ~transitive ~accessed_through_class ~special_method ~include_generated_attributes:true ?dependency (attribute_resolution resolution) ~attribute_name:name class_name |> access let attribute_from_annotation ?special_method resolution ~parent:annotation ~name = match Type.resolve_class annotation with | None -> None | Some [] -> None | Some [{ instantiated; accessed_through_class; class_name }] -> attribute_from_class_name ~resolution ~transitive:true ~instantiated ~accessed_through_class ~name ?special_method class_name >>= fun attribute -> Option.some_if (AnnotatedAttribute.defined attribute) attribute | Some (_ :: _) -> None let get_typed_dictionary ~resolution:({ dependency; _ } as resolution) = AttributeResolution.ReadOnly.get_typed_dictionary (attribute_resolution resolution) ?dependency let is_typed_dictionary ~resolution:({ dependency; _ } as resolution) annotation = Type.primitive_name annotation >>| ClassMetadataEnvironment.ReadOnly.is_typed_dictionary (class_metadata_environment resolution) ?dependency |> Option.value ~default:false let resolved_type = WeakenMutableLiterals.resolved_type let is_consistent_with ({ dependency; _ } as resolution) ~resolve left right ~expression = let comparator = AttributeResolution.ReadOnly.constraints_solution_exists ?dependency (attribute_resolution resolution) in let left = WeakenMutableLiterals.weaken_mutable_literals resolve ~get_typed_dictionary:(get_typed_dictionary ~resolution) ~expression ~resolved:left ~expected:right ~comparator |> resolved_type in comparator ~get_typed_dictionary_override:(fun _ -> None) ~left ~right let is_transitive_successor ?placeholder_subclass_extends_all resolution ~predecessor ~successor = let class_hierarchy = class_hierarchy resolution in ClassHierarchy.is_transitive_successor ?placeholder_subclass_extends_all class_hierarchy ~source:predecessor ~target:successor (* There isn't a great way of testing whether a file only contains tests in Python. Due to the difficulty of handling nested classes within test cases, etc., we use the heuristic that a class which inherits from unittest.TestCase indicates that the entire file is a test file. *) let source_is_unit_test resolution ~source = let is_unittest { Node.value = { Class.name; _ }; _ } = try is_transitive_successor ~placeholder_subclass_extends_all:false resolution ~predecessor:(Reference.show name) ~successor:"unittest.case.TestCase" with | ClassHierarchy.Untracked _ -> false in List.exists (Preprocessing.classes source) ~f:is_unittest let constraints ~resolution:({ dependency; _ } as resolution) = AttributeResolution.ReadOnly.constraints ?dependency (attribute_resolution resolution) let successors ~resolution:({ dependency; _ } as resolution) = ClassMetadataEnvironment.ReadOnly.successors ?dependency (class_metadata_environment resolution) let immediate_parents ~resolution = ClassHierarchy.immediate_parents (class_hierarchy resolution) let attributes ~resolution:({ dependency; _ } as resolution) ?(transitive = false) ?(accessed_through_class = false) ?(include_generated_attributes = true) name = AttributeResolution.ReadOnly.all_attributes (attribute_resolution resolution) ~transitive ~accessed_through_class ~include_generated_attributes name ?dependency let instantiate_attribute ~resolution:({ dependency; _ } as resolution) ?instantiated = AttributeResolution.ReadOnly.instantiate_attribute (attribute_resolution resolution) ?dependency ?instantiated let metaclass ~resolution:({ dependency; _ } as resolution) = AttributeResolution.ReadOnly.metaclass ?dependency (attribute_resolution resolution) let resolve_mutable_literals ({ dependency; _ } as resolution) = AttributeResolution.ReadOnly.resolve_mutable_literals ?dependency (attribute_resolution resolution) let resolve_define ~resolution:({ dependency; _ } as resolution) = AttributeResolution.ReadOnly.resolve_define ?dependency (attribute_resolution resolution) let signature_select ~global_resolution:({ dependency; _ } as resolution) = AttributeResolution.ReadOnly.signature_select ?dependency (attribute_resolution resolution) let legacy_resolve_exports ({ dependency; _ } as resolution) ~reference = UnannotatedGlobalEnvironment.ReadOnly.legacy_resolve_exports ?dependency (unannotated_global_environment resolution) reference let resolve_exports ({ dependency; _ } as resolution) ?from reference = UnannotatedGlobalEnvironment.ReadOnly.resolve_exports ?dependency (unannotated_global_environment resolution) ?from reference let check_invalid_type_parameters ({ dependency; _ } as resolution) = AttributeResolution.ReadOnly.check_invalid_type_parameters (attribute_resolution resolution) ?dependency let variables ?default ({ dependency; _ } as resolution) = ClassHierarchyEnvironment.ReadOnly.variables ?default ?dependency (class_hierarchy_environment resolution) module ConstraintsSet = struct include ConstraintsSet let add constraints ~new_constraint ~global_resolution = TypeOrder.OrderedConstraintsSet.add constraints ~new_constraint ~order:(full_order global_resolution) let solve constraints ~global_resolution = TypeOrder.OrderedConstraintsSet.solve constraints ~order:(full_order global_resolution) module Solution = struct include ConstraintsSet.Solution end end let constraints_solution_exists ({ dependency; _ } as resolution) = AttributeResolution.ReadOnly.constraints_solution_exists ~get_typed_dictionary_override:(fun _ -> None) ?dependency (attribute_resolution resolution) let extract_type_parameters resolution ~source ~target = match source with | Type.Top | Bottom | Any -> (* TODO (T63159626): These special cases may not make sense. *) None | _ -> ClassHierarchy.variables (class_hierarchy resolution) target >>= fun variables -> let namespace = Type.Variable.Namespace.create_fresh () in List.map variables ~f:(Type.Variable.namespace ~namespace) |> Type.Variable.all_unary >>= fun unaries -> let solve_against = List.map unaries ~f:(fun unary -> Type.Parameter.Single (Type.Variable unary)) |> Type.parametric target in ConstraintsSet.add ConstraintsSet.empty ~new_constraint:(LessOrEqual { left = source; right = solve_against }) ~global_resolution:resolution |> ConstraintsSet.solve ~global_resolution:resolution >>= fun solution -> List.map unaries ~f:(ConstraintsSet.Solution.instantiate_single_variable solution) |> Option.all let type_of_iteration_value ~global_resolution iterator_type = match extract_type_parameters global_resolution ~target:"typing.Iterable" ~source:iterator_type with | Some [iteration_type] -> Some iteration_type | _ -> None (* Determine the appropriate type for `yield` expressions in a generator function, based on the return annotation. *) let type_of_generator_send_and_return ~global_resolution generator_type = (* First match against Generator *) match extract_type_parameters global_resolution ~target:"typing.Generator" ~source:generator_type with | Some [_yield_type; send_type; return_type] -> send_type, return_type | _ -> ( (* Fall back to match against AsyncGenerator. We fall back instead of using an explicit flag because, if the user mixes these types up we still ought to resolve their yield expressions to reasonable types *) match extract_type_parameters global_resolution ~target:"typing.AsyncGenerator" ~source:generator_type with | Some [_yield_type; send_type] -> send_type, Type.none | _ -> (* Fall back to Type.none because it's legal to use other annotations like `object` or `Iterator` on a generator function, but in those cases the send type is always NoneType *) Type.none, Type.none) let parse_annotation ({ dependency; _ } as resolution) = AttributeResolution.ReadOnly.parse_annotation ?dependency (attribute_resolution resolution) let resolve_literal ({ dependency; _ } as resolution) = AttributeResolution.ReadOnly.resolve_literal ?dependency (attribute_resolution resolution) let parse_as_parameter_specification_instance_annotation ({ dependency; _ } as resolution) = AliasEnvironment.ReadOnly.parse_as_parameter_specification_instance_annotation (alias_environment resolution) ?dependency let annotation_parser ?(allow_invalid_type_parameters = false) resolution = let validation = if allow_invalid_type_parameters then SharedMemoryKeys.ParseAnnotationKey.ValidatePrimitives else ValidatePrimitivesAndTypeParameters in { AnnotatedCallable.parse_annotation = parse_annotation ~validation resolution; parse_as_parameter_specification_instance_annotation = parse_as_parameter_specification_instance_annotation resolution; } let attribute_names ~resolution:({ dependency; _ } as resolution) ?(transitive = false) ?(accessed_through_class = false) ?(include_generated_attributes = true) ?instantiated:_ name = AttributeResolution.ReadOnly.attribute_names (attribute_resolution resolution) ~transitive ~accessed_through_class ~include_generated_attributes name ?dependency let global_location ({ dependency; _ } as resolution) = AnnotatedGlobalEnvironment.ReadOnly.get_global_location (annotated_global_environment resolution) ?dependency let class_exists ({ dependency; _ } as resolution) = UnannotatedGlobalEnvironment.ReadOnly.class_exists (unannotated_global_environment resolution) ?dependency let overrides class_name ~resolution ~name = let find_override parent = attribute_from_class_name ~transitive:false ~accessed_through_class:true ~name parent ~resolution ~instantiated:(Primitive class_name) >>= fun attribute -> Option.some_if (AnnotatedAttribute.defined attribute) attribute in successors class_name ~resolution |> List.find_map ~f:find_override let define resolution decorator_name = (* Nested function bodies are empty by default. We have to fill them in. *) let rec get_define define_name = function_definition resolution define_name >>| FunctionDefinition.all_bodies >>= function | [{ Node.value = { Define.body; _ } as define; location }] -> let transform_statement = function | { Node.value = Statement.Define { body = []; signature = { name = define_name; _ }; _ }; _; } as statement -> get_define define_name >>| (fun define -> { Node.value = Statement.Define define; location }) |> Option.value ~default:statement | statement -> statement in { define with body = List.map body ~f:transform_statement } |> Option.some (* Ignore functions that have overloads. *) | _ -> None in get_define decorator_name let refine ~global_resolution annotation refined_type = let solve_less_or_equal ~left ~right = ConstraintsSet.add ConstraintsSet.empty ~new_constraint:(LessOrEqual { left; right }) ~global_resolution |> ConstraintsSet.solve ~global_resolution >>| fun solution -> ConstraintsSet.Solution.instantiate solution left in let type_less_or_equal = less_or_equal global_resolution in Annotation.refine ~type_less_or_equal ~solve_less_or_equal ~refined_type annotation