source/analysis/constraintsSet.ml (1,100 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 Assumptions
type class_hierarchy = {
instantiate_successors_parameters:
source:Type.t -> target:Type.Primitive.t -> Type.Parameter.t list option;
is_transitive_successor: source:Type.Primitive.t -> target:Type.Primitive.t -> bool;
variables: Type.Primitive.t -> Type.Variable.t list option;
least_upper_bound: Type.Primitive.t -> Type.Primitive.t -> Type.Primitive.t list;
}
type order = {
class_hierarchy: class_hierarchy;
all_attributes:
Type.t -> assumptions:Assumptions.t -> AnnotatedAttribute.instantiated list option;
attribute:
Type.t ->
assumptions:Assumptions.t ->
name:Ast.Identifier.t ->
AnnotatedAttribute.instantiated option;
is_protocol: Type.t -> protocol_assumptions:ProtocolAssumptions.t -> bool;
get_typed_dictionary: Type.t -> Type.t Type.Record.TypedDictionary.record option;
metaclass: Type.Primitive.t -> assumptions:Assumptions.t -> Type.t option;
assumptions: Assumptions.t;
}
module Solution = struct
(* For now we're just arbitrarily picking the first solution, but we want to allow us the
opportunity to augment that behavior in the future *)
include TypeConstraints.Solution
end
type t = TypeConstraints.t list [@@deriving show]
let empty = [TypeConstraints.empty]
let impossible = []
let potentially_satisfiable constraints_set = List.is_empty constraints_set |> not
type kind =
| LessOrEqual of {
left: Type.t;
right: Type.t;
}
| OrderedTypesLessOrEqual of {
left: Type.OrderedTypes.t;
right: Type.OrderedTypes.t;
}
| VariableIsExactly of Type.Variable.pair
module type OrderedConstraintsSetType = sig
val add : t -> new_constraint:kind -> order:order -> t
val solve : ?only_solve_for:Type.Variable.t list -> t -> order:order -> Solution.t option
val get_parameter_specification_possibilities
: t ->
order:order ->
parameter_specification:Type.Variable.Variadic.Parameters.t ->
Type.Callable.parameters list
val instantiate_protocol_parameters
: order ->
candidate:Type.t ->
protocol:Ast.Identifier.t ->
Type.Parameter.t list option
end
let resolve_callable_protocol
~assumption
~order:{ attribute; assumptions = { callable_assumptions; _ } as assumptions; _ }
annotation
=
match
CallableAssumptions.find_assumed_callable_type ~candidate:annotation callable_assumptions
with
| Some assumption -> Some assumption
| None -> (
let new_assumptions =
{
assumptions with
callable_assumptions =
CallableAssumptions.add ~candidate:annotation ~callable:assumption callable_assumptions;
}
in
attribute annotation ~assumptions:new_assumptions ~name:"__call__"
>>| AnnotatedAttribute.annotation
>>| Annotation.annotation
>>= fun annotation ->
match annotation with
| Type.Parametric { name = "BoundMethod"; parameters = [Single _; Single _] }
| Callable _ ->
Some annotation
| _ -> None)
module type OrderedConstraintsType = TypeConstraints.OrderedConstraintsType with type order = order
module Make (OrderedConstraints : OrderedConstraintsType) = struct
(* TODO(T41127207): merge this with actual signature select *)
let rec simulate_signature_select
order
~callable:{ Type.Callable.implementation; overloads; _ }
~called_as
~constraints
=
let open Callable in
let solve implementation ~initial_constraints =
try
let rec solve_parameters_against_tuple_variadic
~is_lower_bound
~concretes
~ordered_type
~remaining_parameters
=
let before_first_keyword, after_first_keyword_inclusive =
let is_not_keyword_only = function
| Type.Callable.Parameter.Keywords _
| Type.Callable.Parameter.KeywordOnly _ ->
false
| _ -> true
in
List.split_while concretes ~f:is_not_keyword_only
in
let solve_remaining_parameters constraints =
if is_lower_bound then
solve_parameters
~left_parameters:after_first_keyword_inclusive
~right_parameters:remaining_parameters
constraints
else
solve_parameters
~left_parameters:remaining_parameters
~right_parameters:after_first_keyword_inclusive
constraints
in
let add_bound concretes =
let left, right =
if is_lower_bound then
concretes, ordered_type
else
ordered_type, concretes
in
solve_ordered_types_less_or_equal order ~left ~right ~constraints
in
let ordered_type_from_non_keyword_parameters =
let extract_component = function
| Type.Callable.Parameter.PositionalOnly { annotation; _ } ->
Some (Type.OrderedTypes.Concrete [annotation])
| Named { annotation; _ } when not is_lower_bound ->
(* Named arguments can be called positionally, but positionals can't be called
with a name *)
Some (Type.OrderedTypes.Concrete [annotation])
| Variable (Concatenation concatenation) ->
Some (Type.OrderedTypes.Concatenation concatenation)
| _ -> None
in
let concatenate left right =
left >>= fun left -> Type.OrderedTypes.concatenate ~left ~right
in
List.map before_first_keyword ~f:extract_component
|> Option.all
>>= List.fold ~init:(Some (Type.OrderedTypes.Concrete [])) ~f:concatenate
in
ordered_type_from_non_keyword_parameters
>>| add_bound
>>| List.concat_map ~f:solve_remaining_parameters
|> Option.value ~default:impossible
and solve_parameters ~left_parameters ~right_parameters constraints =
match left_parameters, right_parameters with
| Parameter.PositionalOnly _ :: _, Parameter.Named _ :: _ -> []
| ( Parameter.PositionalOnly { annotation = left_annotation; _ } :: left_parameters,
Parameter.PositionalOnly { annotation = right_annotation; _ } :: right_parameters )
| ( Parameter.Named { annotation = left_annotation; _ } :: left_parameters,
Parameter.PositionalOnly { annotation = right_annotation; _ } :: right_parameters ) ->
solve_less_or_equal order ~constraints ~left:right_annotation ~right:left_annotation
|> List.concat_map ~f:(solve_parameters ~left_parameters ~right_parameters)
| ( Parameter.Variable (Concrete left_annotation) :: left_parameters,
Parameter.Variable (Concrete right_annotation) :: right_parameters )
| ( Parameter.Keywords left_annotation :: left_parameters,
Parameter.Keywords right_annotation :: right_parameters ) ->
solve_less_or_equal order ~constraints ~left:right_annotation ~right:left_annotation
|> List.concat_map ~f:(solve_parameters ~left_parameters ~right_parameters)
| ( Parameter.KeywordOnly ({ annotation = left_annotation; _ } as left) :: left_parameters,
Parameter.KeywordOnly ({ annotation = right_annotation; _ } as right)
:: right_parameters )
| ( Parameter.Named ({ annotation = left_annotation; _ } as left) :: left_parameters,
Parameter.Named ({ annotation = right_annotation; _ } as right) :: right_parameters )
| ( Parameter.Named ({ annotation = left_annotation; default = true; _ } as left)
:: left_parameters,
Parameter.KeywordOnly ({ annotation = right_annotation; _ } as right)
:: right_parameters )
| ( Parameter.Named ({ annotation = left_annotation; default = false; _ } as left)
:: left_parameters,
Parameter.KeywordOnly ({ annotation = right_annotation; default = false; _ } as right)
:: right_parameters ) ->
if Parameter.names_compatible (Parameter.Named left) (Parameter.Named right) then
solve_less_or_equal order ~constraints ~left:right_annotation ~right:left_annotation
|> List.concat_map ~f:(solve_parameters ~left_parameters ~right_parameters)
else
impossible
| ( Parameter.Variable (Concrete left_annotation) :: _,
Parameter.PositionalOnly { annotation = right_annotation; _ } :: right_parameters ) ->
solve_less_or_equal order ~constraints ~left:right_annotation ~right:left_annotation
|> List.concat_map ~f:(solve_parameters ~left_parameters ~right_parameters)
| ( Parameter.Variable (Concatenation left) :: left_parameters,
Parameter.Variable (Concatenation right) :: right_parameters ) ->
solve_ordered_types_less_or_equal
order
~left:(Type.OrderedTypes.Concatenation left)
~right:(Type.OrderedTypes.Concatenation right)
~constraints
|> List.concat_map ~f:(solve_parameters ~left_parameters ~right_parameters)
| left, Parameter.Variable (Concatenation concatenation) :: remaining_parameters ->
solve_parameters_against_tuple_variadic
~is_lower_bound:false
~concretes:left
~ordered_type:(Concatenation concatenation)
~remaining_parameters
| Parameter.Variable (Concatenation concatenation) :: remaining_parameters, right ->
solve_parameters_against_tuple_variadic
~is_lower_bound:true
~concretes:right
~ordered_type:(Concatenation concatenation)
~remaining_parameters
| ( Parameter.Variable (Concrete variable_annotation)
:: Parameter.Keywords keywords_annotation :: _,
Parameter.Named { annotation = named_annotation; _ } :: right_parameters ) ->
if Type.equal variable_annotation keywords_annotation then
solve_less_or_equal
order
~constraints
~left:named_annotation
~right:keywords_annotation
|> List.concat_map ~f:(solve_parameters ~left_parameters ~right_parameters)
else
impossible
| Parameter.Variable _ :: left_parameters, right_parameters
| Parameter.Keywords _ :: left_parameters, right_parameters ->
solve_parameters ~left_parameters ~right_parameters constraints
| left :: left_parameters, [] ->
if Parameter.default left then
solve_parameters ~left_parameters ~right_parameters:[] constraints
else
impossible
| [], [] -> [constraints]
| _ -> impossible
in
match implementation.parameters, called_as.parameters with
| Undefined, Undefined -> [initial_constraints]
| Defined implementation, Defined called_as ->
solve_parameters
~left_parameters:implementation
~right_parameters:called_as
initial_constraints
(* We exhibit unsound behavior when a concrete callable is passed into one expecting a
Callable[..., T] - Callable[..., T] admits any parameters, which is not true when a
callable that doesn't admit kwargs and varargs is passed in. We need this since there is
no good way of representing "leave the parameters alone and change the return type" in
the Python type system at the moment. *)
| Defined _, Undefined -> [initial_constraints]
| Undefined, Defined _ -> [initial_constraints]
| bound, ParameterVariadicTypeVariable { head = []; variable }
when Type.Variable.Variadic.Parameters.is_free variable ->
let pair = Type.Variable.ParameterVariadicPair (variable, bound) in
OrderedConstraints.add_upper_bound initial_constraints ~order ~pair |> Option.to_list
| bound, ParameterVariadicTypeVariable { head; variable }
when Type.Variable.Variadic.Parameters.is_free variable ->
let constraints, remainder =
match bound with
| Undefined -> [initial_constraints], Undefined
| ParameterVariadicTypeVariable { head = left_head; variable = left_variable } ->
let paired, remainder = List.split_n left_head (List.length head) in
( solve_ordered_types_less_or_equal
order
~left:(Type.OrderedTypes.Concrete paired)
~right:(Type.OrderedTypes.Concrete head)
~constraints:initial_constraints,
ParameterVariadicTypeVariable { head = remainder; variable = left_variable } )
| Defined defined ->
let paired, remainder = List.split_n defined (List.length head) in
( solve_parameters
~left_parameters:paired
~right_parameters:(Type.Callable.prepend_anonymous_parameters ~head ~tail:[])
initial_constraints,
Defined remainder )
in
let pair = Type.Variable.ParameterVariadicPair (variable, remainder) in
List.filter_map constraints ~f:(OrderedConstraints.add_upper_bound ~order ~pair)
| ParameterVariadicTypeVariable left, ParameterVariadicTypeVariable right
when Type.Callable.equal_parameter_variadic_type_variable Type.equal left right ->
[initial_constraints]
| _, _ -> impossible
with
| _ -> impossible
in
let overload_to_instantiated_return_and_altered_constraints overload =
let namespace = Type.Variable.Namespace.create_fresh () in
let namespaced_variables =
Type.Callable { Type.Callable.kind = Anonymous; implementation = overload; overloads = [] }
|> Type.Variable.all_free_variables
|> List.map ~f:(Type.Variable.namespace ~namespace)
in
let overload =
map_implementation overload ~f:(Type.Variable.namespace_all_free_variables ~namespace)
in
let does_not_leak_namespaced_variables (external_constraints, _) =
not (TypeConstraints.exists_in_bounds external_constraints ~variables:namespaced_variables)
in
let instantiate_return (external_constraints, partial_solution) =
let instantiated_return =
TypeConstraints.Solution.instantiate partial_solution overload.annotation
|> Type.Variable.mark_all_free_variables_as_escaped ~specific:namespaced_variables
in
instantiated_return, external_constraints
in
solve overload ~initial_constraints:constraints
|> List.map
~f:(OrderedConstraints.extract_partial_solution ~order ~variables:namespaced_variables)
|> List.concat_map ~f:Option.to_list
|> List.filter ~f:does_not_leak_namespaced_variables
|> List.map ~f:instantiate_return
in
let overloads =
if List.is_empty overloads then
[implementation]
else if Type.Callable.Overload.is_undefined implementation then
overloads
else
(* TODO(T41195241) always ignore implementation when has overloads. Currently put
implementation as last resort *)
overloads @ [implementation]
in
List.concat_map overloads ~f:overload_to_instantiated_return_and_altered_constraints
(* ## solve_less_or_equal, a brief explanation: ##
At the heart of our handling of generics is this function, solve_less_or_equal.
This function takes:
* a statement of the form F(T1, T2, ... Tn) =<= G(T1, T2, ... Tn), where F and G are types
which may contain any number of free type variables. In this context a free type variable is
one whose value we are trying to determine. This could be a function level generic, an
"escaped" type variable from some sort of incomplete initialization, or even some sort of
synthetic type variable we're using to answer a question like, "if this is an iterable, what
kind of iterable is it?" for correctly handling *args parameters.
* a precondition set of constraints (as defined in TypeConstraints.mli) from a previous call to
solve_less_or_equal (or from somewhere else). This is how you're able to define conjunctions of
=<= statements, as when you are trying to satisfy a number of argument <-> parameter pairs in
signature selection
and returns:
* an arbitrarily ordered list of constraints (again as defined in Typeconstraints.mli) that
each are sufficient to satisfy the given statement and the precondition constraints. If this
list is empty, there is no way to satify those requirements (at least as well as we can know
that).
The general strategy undertaken to achieve this behavior is to pairwise recursively break up
the types in the same way, e.g. we expect to get a tuple on the right if we have a tuple on the
left, and to handle that by enforcing that each of the contained types be less than their pair
on the other side (as tuples are covariant). Certain pairs, such as X =<= Union[...] or
comparing callables with overloads naturally create multiple disjoint possibilities which give
rise to the list of constraints that we end up returning.
Once you have enforced all of the statements you would like to ensure are true, you can extract
possible solutions to the constraints set you have built up with List.filter_map
~f:OrderedConstraints.solve *)
and solve_less_or_equal
({
class_hierarchy =
{ instantiate_successors_parameters; variables; is_transitive_successor; _ };
is_protocol;
assumptions = { protocol_assumptions; _ } as assumptions;
get_typed_dictionary;
metaclass;
_;
} as order)
~constraints
~left
~right
=
let open Type.Record.TypedDictionary in
let add_fallbacks other =
Type.Variable.all_free_variables other
|> List.fold ~init:constraints ~f:OrderedConstraints.add_fallback_to_any
in
let solve_less_or_equal_primitives ~source ~target =
if is_transitive_successor ~source ~target then
[constraints]
else if
is_protocol right ~protocol_assumptions
&& [%compare.equal: Type.Parameter.t list option]
(instantiate_protocol_parameters order ~candidate:left ~protocol:target)
(Some [])
then
[constraints]
else
impossible
in
match left, right with
| _, _ when Type.equal left right -> [constraints]
| _, Type.Primitive "object" -> [constraints]
| other, Type.Any -> [add_fallbacks other]
| _, Type.Top -> [constraints]
| Type.ParameterVariadicComponent component, _ ->
let left =
match Type.Variable.Variadic.Parameters.Components.component component with
| KeywordArguments ->
Type.parametric
Type.mapping_primitive
[Single Type.string; Single Type.object_primitive]
| PositionalArguments ->
Type.Tuple (Type.OrderedTypes.create_unbounded_concatenation Type.object_primitive)
in
solve_less_or_equal order ~constraints ~left ~right
| _, Type.ParameterVariadicComponent _ -> impossible
| Type.Annotated left, _ -> solve_less_or_equal order ~constraints ~left ~right
| _, Type.Annotated right -> solve_less_or_equal order ~constraints ~left ~right
| Type.Any, other -> [add_fallbacks other]
| Type.Variable left_variable, Type.Variable right_variable
when Type.Variable.Unary.is_free left_variable && Type.Variable.Unary.is_free right_variable
->
(* Either works because constraining V1 to be less or equal to V2 implies that V2 is greater
than or equal to V1. Therefore either constraint is sufficient, and we should consider
both. This approach simplifies things downstream for the constraint solver *)
let right_greater_than_left, left_less_than_right =
( OrderedConstraints.add_lower_bound
constraints
~order
~pair:(Type.Variable.UnaryPair (right_variable, left))
|> Option.to_list,
OrderedConstraints.add_upper_bound
constraints
~order
~pair:(Type.Variable.UnaryPair (left_variable, right))
|> Option.to_list )
in
right_greater_than_left @ left_less_than_right
| Type.Variable variable, bound when Type.Variable.Unary.is_free variable ->
let pair = Type.Variable.UnaryPair (variable, bound) in
OrderedConstraints.add_upper_bound constraints ~order ~pair |> Option.to_list
| bound, Type.Variable variable when Type.Variable.Unary.is_free variable ->
let pair = Type.Variable.UnaryPair (variable, bound) in
OrderedConstraints.add_lower_bound constraints ~order ~pair |> Option.to_list
| _, Type.Bottom
| Type.Top, _ ->
impossible
| Type.Bottom, _ -> [constraints]
| _, Type.NoneType -> impossible
| _, Type.RecursiveType recursive_type ->
if
[%compare.equal: Type.Parameter.t list option]
(instantiate_recursive_type_parameters order ~candidate:left ~recursive_type)
(Some [])
then
[constraints]
else
impossible
| Type.RecursiveType recursive_type, _ ->
solve_less_or_equal
order
~constraints
~left:(Type.RecursiveType.unfold_recursive_type recursive_type)
~right
| (Type.Callable _ | Type.NoneType), Type.Primitive protocol
when is_protocol right ~protocol_assumptions ->
if
[%compare.equal: Type.Parameter.t list option]
(instantiate_protocol_parameters order ~protocol ~candidate:left)
(Some [])
then
[constraints]
else
impossible
| (Type.Callable _ | Type.NoneType), Type.Parametric { name; _ }
when is_protocol right ~protocol_assumptions ->
instantiate_protocol_parameters order ~protocol:name ~candidate:left
>>| Type.parametric name
>>| (fun left -> solve_less_or_equal order ~constraints ~left ~right)
|> Option.value ~default:impossible
| Type.Union lefts, right ->
solve_ordered_types_less_or_equal
order
~left:(Concrete lefts)
~right:(Concrete (List.map lefts ~f:(fun _ -> right)))
~constraints
| Type.NoneType, Type.Union rights when List.exists rights ~f:Type.is_none ->
(* Technically speaking, removing this special-case still leads to correct, but somewhat
redundant solutions, when `rights` contains both None and type varaibles *)
[constraints]
| Type.NoneType, Type.Union rights ->
List.concat_map rights ~f:(fun right -> solve_less_or_equal order ~constraints ~left ~right)
| Type.NoneType, _ -> impossible
(* We have to consider both the variables' constraint and its full value against the union. *)
| Type.Variable bound_variable, Type.Union union ->
solve_less_or_equal
order
~constraints
~left:(Type.Variable.Unary.upper_bound bound_variable)
~right
@ List.concat_map
~f:(fun right -> solve_less_or_equal order ~constraints ~left ~right)
union
| Type.Variable bound_variable, _ ->
solve_less_or_equal
order
~constraints
~left:(Type.Variable.Unary.upper_bound bound_variable)
~right
| _, Type.Variable _bound_variable -> impossible
| Type.IntExpression _, _
| _, Type.IntExpression _ ->
Type.solve_less_or_equal_polynomial
~left
~right
~impossible
~solve:(solve_less_or_equal order ~constraints)
| _, Type.Union rights ->
if
Type.Variable.all_variables_are_resolved left
&& Type.Variable.all_variables_are_resolved right
then
(* This is a pure performance optimization, but is practically mandatory because there are
some humongous unions out there *)
let simple_solve right =
solve_less_or_equal order ~constraints ~left ~right |> List.is_empty |> not
in
if List.exists rights ~f:simple_solve then
[constraints]
else
impossible
else if
Type.Variable.all_variables_are_resolved left
&& List.exists
~f:(fun right ->
Type.Variable.all_variables_are_resolved right
&& solve_less_or_equal order ~constraints ~left ~right |> List.is_empty |> not)
rights
then (* If X <= Union[Y, Z] with X and Y already resolved and X <= Y *)
[constraints]
else
List.concat_map rights ~f:(fun right ->
solve_less_or_equal order ~constraints ~left ~right)
| ( Type.Parametric { name = "type"; parameters = [Single left] },
Type.Parametric { name = "type"; parameters = [Single right] } ) ->
solve_less_or_equal order ~constraints ~left ~right
| Type.Parametric { name = "type"; parameters = [Single meta_parameter] }, _ ->
let through_meta_hierarchy =
match meta_parameter, right with
| Primitive meta_parameter, Primitive _ ->
metaclass meta_parameter ~assumptions
>>| (fun left -> solve_less_or_equal order ~left ~right ~constraints)
|> Option.value ~default:impossible
| _ -> impossible
in
let through_protocol_hierarchy =
match right, is_protocol right ~protocol_assumptions with
| Primitive right_name, true ->
if
[%compare.equal: Type.Parameter.t list option]
(instantiate_protocol_parameters order ~candidate:left ~protocol:right_name)
(Some [])
then
[constraints]
else
impossible
| Parametric { name = right_name; _ }, true ->
instantiate_protocol_parameters order ~protocol:right_name ~candidate:left
>>| Type.parametric right_name
>>| (fun left -> solve_less_or_equal order ~left ~right ~constraints)
|> Option.value ~default:impossible
| Callable _, _ -> (
match meta_parameter with
| Type.Union types ->
solve_less_or_equal
order
~constraints
~left:(Type.union (List.map ~f:Type.meta types))
~right
| _ ->
resolve_callable_protocol ~order ~assumption:right left
>>| (fun left -> solve_less_or_equal order ~constraints ~left ~right)
|> Option.value ~default:impossible)
| _ -> impossible
in
List.append through_protocol_hierarchy through_meta_hierarchy
| _, Type.Parametric { name = right_name; parameters = right_parameters } ->
let solve_respecting_variance constraints = function
| Type.Variable.UnaryPair (unary, left), Type.Variable.UnaryPair (_, right) -> (
match left, right, unary with
(* TODO kill these special cases *)
| Type.Bottom, _, _ ->
(* T[Bottom] is a subtype of T[_T2], for any _T2 and regardless of its variance. *)
constraints
| _, Type.Top, _ ->
(* T[_T2] is a subtype of T[Top], for any _T2 and regardless of its variance. *)
constraints
| Top, _, _ -> impossible
| left, right, { Type.Variable.Unary.variance = Covariant; _ } ->
constraints
|> List.concat_map ~f:(fun constraints ->
solve_less_or_equal order ~constraints ~left ~right)
| left, right, { variance = Contravariant; _ } ->
constraints
|> List.concat_map ~f:(fun constraints ->
solve_less_or_equal order ~constraints ~left:right ~right:left)
| left, right, { variance = Invariant; _ } ->
constraints
|> List.concat_map ~f:(fun constraints ->
solve_less_or_equal order ~constraints ~left ~right)
|> List.concat_map ~f:(fun constraints ->
solve_less_or_equal order ~constraints ~left:right ~right:left))
| ( Type.Variable.ParameterVariadicPair (_, left),
Type.Variable.ParameterVariadicPair (_, right) ) ->
let left = Type.Callable.create ~parameters:left ~annotation:Type.Any () in
let right = Type.Callable.create ~parameters:right ~annotation:Type.Any () in
List.concat_map constraints ~f:(fun constraints ->
solve_less_or_equal order ~constraints ~left ~right)
| Type.Variable.TupleVariadicPair (_, left), Type.Variable.TupleVariadicPair (_, right) ->
(* We assume variadic classes are covariant by default since they represent the
immutable shape of a datatype. *)
constraints
|> List.concat_map ~f:(fun constraints ->
solve_ordered_types_less_or_equal order ~left ~right ~constraints)
| _ -> impossible
in
let solve_parameters left_parameters right_parameters =
variables right_name
>>= Type.Variable.zip_variables_with_two_parameter_lists
~left_parameters
~right_parameters
>>| List.fold ~f:solve_respecting_variance ~init:[constraints]
in
let left_parameters =
let left_parameters = instantiate_successors_parameters ~source:left ~target:right_name in
match left_parameters with
| None when is_protocol right ~protocol_assumptions ->
instantiate_protocol_parameters order ~protocol:right_name ~candidate:left
| _ -> left_parameters
in
left_parameters
>>= (fun left_parameters -> solve_parameters left_parameters right_parameters)
|> Option.value ~default:impossible
| Type.Primitive source, Type.Primitive target -> (
let left_typed_dictionary = get_typed_dictionary left in
let right_typed_dictionary = get_typed_dictionary right in
match left_typed_dictionary, right_typed_dictionary with
| Some { fields = left_fields; _ }, Some { fields = right_fields; _ } ->
let field_not_found field =
not
(List.exists
left_fields
~f:([%equal: Type.t Type.Record.TypedDictionary.typed_dictionary_field] field))
in
if not (List.exists right_fields ~f:field_not_found) then
[constraints]
else
impossible
| Some { fields; _ }, None ->
let left =
Type.Primitive
(Type.TypedDictionary.class_name
~total:(Type.TypedDictionary.are_fields_total fields))
in
solve_less_or_equal order ~constraints ~left ~right
| None, Some { fields; _ } ->
let right =
Type.Primitive
(Type.TypedDictionary.class_name
~total:(Type.TypedDictionary.are_fields_total fields))
in
solve_less_or_equal order ~constraints ~left ~right
| None, None -> solve_less_or_equal_primitives ~source ~target)
| Type.Parametric { name = source; _ }, Type.Primitive target ->
solve_less_or_equal_primitives ~source ~target
(* A <= B -> A <= Optional[B].*)
| Type.Tuple left, Type.Tuple right ->
solve_ordered_types_less_or_equal order ~left ~right ~constraints
| Type.Tuple (Concatenation concatenation), Type.Primitive _ ->
Type.OrderedTypes.Concatenation.extract_sole_unbounded_annotation concatenation
>>| (fun parameter ->
solve_less_or_equal
order
~constraints
~left:(Type.parametric "tuple" [Single parameter])
~right)
|> Option.value ~default:impossible
| Type.Tuple (Concrete members), Type.Primitive _ ->
solve_less_or_equal
order
~constraints
~left:(Type.parametric "tuple" [Single (Type.union members)])
~right
| Type.Primitive name, Type.Tuple _ ->
if Type.Primitive.equal name "tuple" then [constraints] else impossible
| Type.Tuple _, _
| _, Type.Tuple _ ->
impossible
| ( Type.Callable { Callable.kind = Callable.Named left; _ },
Type.Callable { Callable.kind = Callable.Named right; _ } ) ->
if Reference.equal left right then
[constraints]
else
impossible
| ( Type.Callable
{
Callable.kind = Callable.Anonymous;
implementation = left_implementation;
overloads = left_overloads;
},
Type.Callable
{
Callable.kind = Callable.Named _;
implementation = right_implementation;
overloads = right_overloads;
} )
when Callable.equal_overload Type.equal left_implementation right_implementation
&& List.equal (Callable.equal_overload Type.equal) left_overloads right_overloads ->
impossible
| Type.Callable callable, Type.Callable { implementation; overloads; _ } ->
let fold_overload sofar called_as =
let call_as_overload constraints =
simulate_signature_select order ~callable ~called_as ~constraints
|> List.concat_map ~f:(fun (left, constraints) ->
solve_less_or_equal order ~constraints ~left ~right:called_as.annotation)
in
List.concat_map sofar ~f:call_as_overload
in
List.fold (implementation :: overloads) ~f:fold_overload ~init:[constraints]
| left, Type.Callable _ ->
resolve_callable_protocol ~order ~assumption:right left
>>| (fun left -> solve_less_or_equal order ~constraints ~left ~right)
|> Option.value ~default:impossible
| Type.Callable _, _ -> impossible
| Type.Literal (String (LiteralValue _)), Type.Literal (String AnyLiteral) -> [constraints]
| _, Type.Literal _ -> impossible
| Type.Literal _, _ ->
solve_less_or_equal order ~constraints ~left:(Type.weaken_literals left) ~right
| Type.TypeOperation _, _
| _, Type.TypeOperation _ ->
impossible
and solve_ordered_types_less_or_equal order ~left ~right ~constraints =
let solve_non_variadic_pairs ~pairs constraints =
let solve_pair constraints (left, right) =
List.concat_map constraints ~f:(fun constraints ->
solve_less_or_equal order ~constraints ~left ~right)
in
List.fold ~init:[constraints] ~f:solve_pair pairs
in
let open Type.OrderedTypes in
if Type.OrderedTypes.equal left right then
[constraints]
else
let solve_split_ordered_types { prefix_pairs; middle_pair; suffix_pairs } =
match middle_pair with
(* TODO(T84854853): Optimization: Avoid this splitting and concatenating for Concrete vs
Concrete. *)
| Concrete left_middle, Concrete right_middle -> (
match List.zip left_middle right_middle with
| Ok middle_pairs ->
solve_non_variadic_pairs
~pairs:(prefix_pairs @ middle_pairs @ suffix_pairs)
constraints
| Unequal_lengths -> impossible)
| Concrete concrete, Concatenation concatenation -> (
match
( Type.OrderedTypes.Concatenation.extract_sole_variadic concatenation,
Type.OrderedTypes.Concatenation.extract_sole_unbounded_annotation concatenation )
with
| Some variadic, _ when Type.Variable.Variadic.Tuple.is_free variadic ->
solve_non_variadic_pairs ~pairs:(prefix_pairs @ suffix_pairs) constraints
|> List.filter_map
~f:
(OrderedConstraints.add_lower_bound
~order
~pair:(Type.Variable.TupleVariadicPair (variadic, Concrete concrete)))
| _, Some unbounded_element ->
solve_non_variadic_pairs ~pairs:(prefix_pairs @ suffix_pairs) constraints
|> List.concat_map ~f:(fun constraints ->
solve_less_or_equal
order
~constraints
~left:(Type.union concrete)
~right:unbounded_element)
| _ -> impossible)
| Concatenation concatenation, Concrete concrete -> (
match
( Type.OrderedTypes.Concatenation.extract_sole_variadic concatenation,
Type.OrderedTypes.Concatenation.extract_sole_unbounded_annotation concatenation )
with
| Some variadic, _ when Type.Variable.Variadic.Tuple.is_free variadic ->
solve_non_variadic_pairs ~pairs:(prefix_pairs @ suffix_pairs) constraints
|> List.filter_map
~f:
(OrderedConstraints.add_upper_bound
~order
~pair:(Type.Variable.TupleVariadicPair (variadic, Concrete concrete)))
| _ -> impossible)
| Concatenation left_concatenation, Concatenation right_concatenation -> (
match
( Type.OrderedTypes.Concatenation.extract_sole_variadic left_concatenation,
Type.OrderedTypes.Concatenation.extract_sole_variadic right_concatenation )
with
| _, Some variadic when Type.Variable.Variadic.Tuple.is_free variadic ->
solve_non_variadic_pairs ~pairs:(prefix_pairs @ suffix_pairs) constraints
|> List.filter_map
~f:
(OrderedConstraints.add_lower_bound
~order
~pair:
(Type.Variable.TupleVariadicPair
(variadic, Concatenation left_concatenation)))
| Some variadic, _ when Type.Variable.Variadic.Tuple.is_free variadic ->
solve_non_variadic_pairs ~pairs:(prefix_pairs @ suffix_pairs) constraints
|> List.filter_map
~f:
(OrderedConstraints.add_upper_bound
~order
~pair:
(Type.Variable.TupleVariadicPair
(variadic, Concatenation right_concatenation)))
| _ -> (
match
( Type.OrderedTypes.Concatenation.extract_sole_unbounded_annotation
left_concatenation,
Type.OrderedTypes.Concatenation.extract_sole_unbounded_annotation
right_concatenation )
with
| Some left_unbounded_element, Some right_unbounded_element ->
solve_non_variadic_pairs ~pairs:(prefix_pairs @ suffix_pairs) constraints
|> List.concat_map ~f:(fun constraints ->
solve_less_or_equal
order
~constraints
~left:left_unbounded_element
~right:right_unbounded_element)
| None, Some right_unbounded_element ->
solve_non_variadic_pairs ~pairs:(prefix_pairs @ suffix_pairs) constraints
|> List.concat_map ~f:(fun constraints ->
solve_less_or_equal
order
~constraints
~left:Type.object_primitive
~right:right_unbounded_element)
| _ -> impossible))
in
Type.OrderedTypes.split_matching_elements_by_length left right
>>| solve_split_ordered_types
|> Option.value ~default:impossible
(** Find parameters to instantiate `protocol` such that `candidate <: protocol[parameters]`, where
`<:` is `solve_candidate_less_or_equal_protocol`.
NOTE: unlike several of the other methods defined by this `let rec` block, here an empty list
does not mean failure, it means a success with no constraints due to generics. A failure is
indicated by an output of None.
This can handle recursive instances of (candidate <: protocol). The first time it sees a
candidate-protocol pair, it stores an assumed set of parameters. The next time it sees the
same pair, it returns the assumed parameters. When the protocol is not generic, the solution
if it exists will be [].
We need this because Python protocols can refer to themselves. So, the subtyping relation for
protocols is the one for equirecursive types. See section 21.9 of Types and Programming
Languages for the subtyping algorithm.
Note that classes that refer to themselves don't suffer from this since subtyping for two
classes just follows from the class hierarchy. *)
and instantiate_protocol_parameters_with_solve
({
class_hierarchy = { variables; _ };
assumptions = { protocol_assumptions; _ } as assumptions;
_;
} as order)
~solve_candidate_less_or_equal_protocol
~candidate
~protocol
: Type.Parameter.t list option
=
match candidate with
| Type.Primitive candidate_name when Option.is_some (variables candidate_name) ->
(* If we are given a "stripped" generic, we decline to do structural analysis, as these
kinds of comparisons only exists for legacy reasons to do nominal comparisons *)
None
| Type.Primitive candidate_name when Identifier.equal candidate_name protocol -> Some []
| Type.Parametric { name; parameters } when Identifier.equal name protocol -> Some parameters
| _ -> (
let assumed_protocol_parameters =
ProtocolAssumptions.find_assumed_protocol_parameters
protocol_assumptions
~candidate
~protocol
in
match assumed_protocol_parameters with
| Some result -> Some result
| None ->
let protocol_generics = variables protocol in
let protocol_generic_parameters =
protocol_generics >>| List.map ~f:Type.Variable.to_parameter
in
let new_assumptions =
ProtocolAssumptions.add
protocol_assumptions
~candidate
~protocol
~protocol_parameters:(Option.value protocol_generic_parameters ~default:[])
in
let assumptions = { assumptions with protocol_assumptions = new_assumptions } in
let order_with_new_assumption = { order with assumptions } in
let candidate, desanitize_map =
match candidate with
| Type.Callable _ -> candidate, []
| _ ->
(* We don't return constraints for the candidate's free variables, so we must
underapproximate and determine conformance in the worst case *)
let desanitize_map, sanitized_candidate =
let namespace = Type.Variable.Namespace.create_fresh () in
let module SanitizeTransform = Type.Transform.Make (struct
type state = Type.Variable.pair list
let visit_children_before _ _ = true
let visit_children_after = false
let visit sofar = function
| Type.Variable variable when Type.Variable.Unary.is_free variable ->
let transformed_variable =
Type.Variable.Unary.namespace variable ~namespace
|> Type.Variable.Unary.mark_as_bound
in
{
Type.Transform.transformed_annotation =
Type.Variable transformed_variable;
new_state =
Type.Variable.UnaryPair
(transformed_variable, Type.Variable variable)
:: sofar;
}
| transformed_annotation ->
{ Type.Transform.transformed_annotation; new_state = sofar }
end)
in
SanitizeTransform.visit [] candidate
in
sanitized_candidate, desanitize_map
in
let instantiate_protocol_generics solution =
let desanitize =
let desanitization_solution = TypeConstraints.Solution.create desanitize_map in
let instantiate = function
| Type.Parameter.Single single ->
[
Type.Parameter.Single
(TypeConstraints.Solution.instantiate desanitization_solution single);
]
| CallableParameters parameters ->
[
CallableParameters
(TypeConstraints.Solution.instantiate_callable_parameters
desanitization_solution
parameters);
]
| Unpacked unpackable ->
TypeConstraints.Solution.instantiate_ordered_types
desanitization_solution
(Concatenation
(Type.OrderedTypes.Concatenation.create_from_unpackable unpackable))
|> Type.OrderedTypes.to_parameters
in
List.concat_map ~f:instantiate
in
let instantiate = function
| Type.Variable.Unary variable ->
TypeConstraints.Solution.instantiate_single_variable solution variable
|> Option.value ~default:(Type.Variable variable)
|> fun instantiated -> [Type.Parameter.Single instantiated]
| ParameterVariadic variable ->
TypeConstraints.Solution.instantiate_single_parameter_variadic solution variable
|> Option.value
~default:
(Type.Callable.ParameterVariadicTypeVariable { head = []; variable })
|> fun instantiated -> [Type.Parameter.CallableParameters instantiated]
| TupleVariadic variadic ->
TypeConstraints.Solution.instantiate_ordered_types
solution
(Concatenation (Type.OrderedTypes.Concatenation.create variadic))
|> Type.OrderedTypes.to_parameters
in
protocol_generics
>>| List.concat_map ~f:instantiate
>>| desanitize
|> Option.value ~default:[]
in
let protocol_annotation =
protocol_generic_parameters
>>| Type.parametric protocol
|> Option.value ~default:(Type.Primitive protocol)
in
solve_candidate_less_or_equal_protocol
order_with_new_assumption
~candidate
~protocol_annotation
>>| instantiate_protocol_generics)
(** As with `instantiate_protocol_parameters_with_solve`, here `None` means a failure to match
`candidate` type with the protocol, whereas `Some []` means no generic constraints were
induced. *)
and instantiate_protocol_parameters
: order -> candidate:Type.t -> protocol:Ast.Identifier.t -> Type.Parameter.t list option
=
(* A candidate is less-or-equal to a protocol if candidate.x <: protocol.x for each attribute
`x` in the protocol. *)
let solve_all_protocol_attributes_less_or_equal
({ attribute; all_attributes; assumptions; _ } as order)
~candidate
~protocol_annotation
=
let attribute_implements constraints_set protocol_attribute =
match constraints_set with
| [] -> []
| _ ->
let attribute_type attribute =
AnnotatedAttribute.annotation attribute |> Annotation.annotation
in
let attribute_type_with_getattr_fallback ~attribute_lookup ~name =
match attribute_lookup ~name with
| Some found -> Some (attribute_type found)
| None -> (
match attribute_lookup ~name:"__getattr__" >>| attribute_type with
| Some
(Type.Parametric
{ name = "BoundMethod"; parameters = [Single (Type.Callable callable); _] })
| Some (Type.Callable callable) ->
Some callable.implementation.annotation
| _ -> None)
in
attribute_type_with_getattr_fallback
~attribute_lookup:(attribute ~assumptions candidate)
~name:(AnnotatedAttribute.name protocol_attribute)
>>| (fun left ->
let right =
match attribute_type protocol_attribute with
| Type.Parametric { name = "BoundMethod"; _ } as bound_method ->
attribute ~assumptions bound_method ~name:"__call__"
>>| attribute_type
|> Option.value ~default:Type.object_primitive
| annotation -> annotation
in
List.concat_map constraints_set ~f:(fun constraints ->
solve_less_or_equal order ~left ~right ~constraints))
|> Option.value ~default:[]
in
let protocol_attributes =
let is_not_object_or_generic_method attribute =
let parent = AnnotatedAttribute.parent attribute in
(not (Type.is_object (Primitive parent)))
&& not (Type.is_generic_primitive (Primitive parent))
in
all_attributes ~assumptions protocol_annotation
>>| List.filter ~f:is_not_object_or_generic_method
in
protocol_attributes
>>| List.fold ~init:[TypeConstraints.empty] ~f:attribute_implements
>>| List.filter_map ~f:(OrderedConstraints.solve ~order)
>>= List.hd
in
instantiate_protocol_parameters_with_solve
~solve_candidate_less_or_equal_protocol:solve_all_protocol_attributes_less_or_equal
and instantiate_recursive_type_parameters order ~candidate ~recursive_type
: Type.Parameter.t list option
=
(* TODO(T44784951): Allow passing in the generic parameters for generic recursive types. *)
let solve_recursive_type_less_or_equal order ~candidate ~protocol_annotation:_ =
let expanded_body = Type.RecursiveType.unfold_recursive_type recursive_type in
solve_less_or_equal
order
~left:candidate
~right:expanded_body
~constraints:TypeConstraints.empty
|> List.filter_map ~f:(OrderedConstraints.solve ~order)
|> List.hd
in
instantiate_protocol_parameters_with_solve
order
~solve_candidate_less_or_equal_protocol:solve_recursive_type_less_or_equal
~candidate
~protocol:(Type.RecursiveType.name recursive_type)
let add existing_constraints ~new_constraint ~order =
match new_constraint with
| LessOrEqual { left; right } ->
List.concat_map existing_constraints ~f:(fun constraints ->
solve_less_or_equal order ~constraints ~left ~right)
| OrderedTypesLessOrEqual { left; right } ->
List.concat_map existing_constraints ~f:(fun constraints ->
solve_ordered_types_less_or_equal order ~constraints ~left ~right)
| VariableIsExactly pair ->
let add_both_bounds constraints =
OrderedConstraints.add_upper_bound constraints ~order ~pair
>>= OrderedConstraints.add_lower_bound ~order ~pair
in
List.filter_map existing_constraints ~f:add_both_bounds
let solve ?only_solve_for constraints_set ~order =
match only_solve_for with
| Some variables ->
let partial_solve constraints =
OrderedConstraints.extract_partial_solution constraints ~order ~variables >>| snd
in
List.find_map constraints_set ~f:partial_solve
| None -> List.find_map constraints_set ~f:(OrderedConstraints.solve ~order)
let get_parameter_specification_possibilities constraints_set ~order ~parameter_specification =
List.filter_map
constraints_set
~f:
(OrderedConstraints.extract_partial_solution
~order
~variables:[Type.Variable.ParameterVariadic parameter_specification])
|> List.map ~f:snd
|> List.filter_map ~f:(fun solution ->
TypeConstraints.Solution.instantiate_single_parameter_variadic
solution
parameter_specification)
end