source/new_parser/pyreNewParser.ml (1,014 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 Base
module Context = PyreAst.Parser.Context
module Error = PyreAst.Parser.Error
exception Exception of Error.t
exception InternalError of Error.t
let position ~line ~column = { Ast.Location.line; column }
let location ~start ~stop = { Ast.Location.start; stop }
let identifier x = x
let expression_context = PyreAst.TaglessFinal.ExpressionContext.make ~load:() ~store:() ~del:() ()
let constant =
let open Ast.Expression in
let integer i = Constant.Integer i in
let big_integer _ =
(* TODO (T102723192): We should probably mark this case properly. *)
Constant.Integer Int.max_value
in
let float_ f = Constant.Float f in
let complex f = Constant.Complex f in
let string_ s = Constant.String (StringLiteral.create s) in
let byte_string s = Constant.String (StringLiteral.create ~bytes:true s) in
PyreAst.TaglessFinal.Constant.make
~none:Constant.NoneLiteral
~false_:Constant.False
~true_:Constant.True
~ellipsis:Constant.Ellipsis
~integer
~big_integer
~float_
~complex
~string_
~byte_string
()
let boolean_operator =
let open Ast.Expression in
PyreAst.TaglessFinal.BooleanOperator.make ~and_:BooleanOperator.And ~or_:BooleanOperator.Or ()
let binary_operator =
PyreAst.TaglessFinal.BinaryOperator.make
~add:"add"
~sub:"sub"
~mult:"mul"
~matmult:"matmul"
~div:"truediv"
~mod_:"mod"
~pow:"pow"
~lshift:"lshift"
~rshift:"rshift"
~bitor:"or"
~bitxor:"xor"
~bitand:"and"
~floordiv:"floordiv"
()
let unary_operator =
let open Ast.Expression in
PyreAst.TaglessFinal.UnaryOperator.make
~invert:UnaryOperator.Invert
~not_:UnaryOperator.Not
~uadd:UnaryOperator.Positive
~usub:UnaryOperator.Negative
()
let comparison_operator =
let open Ast.Expression in
PyreAst.TaglessFinal.ComparisonOperator.make
~eq:ComparisonOperator.Equals
~noteq:ComparisonOperator.NotEquals
~lt:ComparisonOperator.LessThan
~lte:ComparisonOperator.LessThanOrEquals
~gt:ComparisonOperator.GreaterThan
~gte:ComparisonOperator.GreaterThanOrEquals
~is:ComparisonOperator.Is
~isnot:ComparisonOperator.IsNot
~in_:ComparisonOperator.In
~notin:ComparisonOperator.NotIn
()
let comprehension ~target ~iter ~ifs ~is_async =
let open Ast.Expression in
(* PEP-572 disallows assignment expressions in comprehension iterables. `pyre-ast` does not check
for that. *)
let check_assignment_expression expression =
let mapper =
let open Ast.Location in
let map_walrus_operator
~mapper:_
~location:{ start = { line; column }; stop = { line = end_line; column = end_column } }
_
=
raise
(InternalError
{
Error.line;
column;
end_line;
end_column;
message =
"assignment expression cannot be used in a comprehension iterable expression";
})
in
(* The following 4 items are introduced for optimization purpose: avoid recursing into
comprehension sub-expressions as they are already checked for assignment expressions upon
construction. *)
let map_dictionary_comprehension ~mapper:_ ~location comprehension =
Ast.Node.create ~location (Expression.DictionaryComprehension comprehension)
in
let map_generator ~mapper:_ ~location comprehension =
Ast.Node.create ~location (Expression.Generator comprehension)
in
let map_list_comprehension ~mapper:_ ~location comprehension =
Ast.Node.create ~location (Expression.ListComprehension comprehension)
in
let map_set_comprehension ~mapper:_ ~location comprehension =
Ast.Node.create ~location (Expression.SetComprehension comprehension)
in
Mapper.create_default
~map_walrus_operator
~map_dictionary_comprehension
~map_generator
~map_list_comprehension
~map_set_comprehension
()
in
Mapper.map ~mapper expression
in
{
Comprehension.Generator.target;
iterator = check_assignment_expression iter;
conditions = ifs;
async = is_async;
}
module KeywordArgument = struct
type t = {
location: Ast.Location.t;
name: Ast.Identifier.t option;
value: Ast.Expression.t;
}
end
let keyword ~location ~arg ~value = { KeywordArgument.location; name = arg; value }
let convert_positional_argument value = { Ast.Expression.Call.Argument.name = None; value }
let convert_keyword_argument { KeywordArgument.location; name; value } =
let open Ast.Expression in
let module Node = Ast.Node in
match name with
| None ->
(* CPython quirk: **arg is represented as keyword arg without a name. *)
{
Call.Argument.name = None;
value = Expression.Starred (Starred.Twice value) |> Node.create ~location;
}
| Some keyword_name -> { Call.Argument.name = Some (Node.create ~location keyword_name); value }
module SingleParameter = struct
type t = {
location: Ast.Location.t;
identifier: Ast.Identifier.t;
annotation: Ast.Expression.t option;
}
end
let argument ~location ~identifier ~annotation ~type_comment =
let annotation =
match annotation with
| Some _ -> annotation
| None -> (
match type_comment with
| None -> None
| Some comment ->
let comment_annotation =
Ast.Expression.(
Expression.Constant
(Constant.String { StringLiteral.kind = String; value = comment }))
in
Some (Ast.Node.create ~location comment_annotation))
in
{ SingleParameter.location; identifier; annotation }
let arguments ~posonlyargs ~args ~vararg ~kwonlyargs ~kw_defaults ~kwarg ~defaults =
let open Ast.Expression in
let module Node = Ast.Node in
let to_parameter ({ SingleParameter.location; identifier; annotation }, default_value) =
{ Parameter.name = identifier; value = default_value; annotation } |> Node.create ~location
in
let to_parameters parameter_list default_list =
List.zip_exn parameter_list default_list |> List.map ~f:to_parameter
in
let positional_only_defaults, regular_defaults =
let positional_only_count = List.length posonlyargs in
let regular_count = List.length args in
let expanded_defaults =
let total_counts = positional_only_count + regular_count in
let fill_counts = total_counts - List.length defaults in
List.map defaults ~f:Option.some |> List.append (List.init fill_counts ~f:(fun _ -> None))
in
List.split_n expanded_defaults positional_only_count
in
let positional_only_parameters = to_parameters posonlyargs positional_only_defaults in
let regular_parameters = to_parameters args regular_defaults in
let keyword_only_parameters = to_parameters kwonlyargs kw_defaults in
let vararg_parameter =
let handle_vararg { SingleParameter.location; identifier; annotation } =
let name = Caml.Format.sprintf "*%s" identifier in
{ Parameter.name; value = None; annotation } |> Node.create ~location
in
Option.map vararg ~f:handle_vararg
in
let kwarg_parameter =
let handle_kwarg { SingleParameter.location; identifier; annotation } =
let name = Caml.Format.sprintf "**%s" identifier in
{ Parameter.name; value = None; annotation } |> Node.create ~location
in
Option.map kwarg ~f:handle_kwarg
in
let delimiter_parameter ~should_insert name =
(* TODO(T101307161): This is just an ugly temporary hack that helps preserve backward
compatibility. *)
if should_insert then
[Node.create_with_default_location { Parameter.name; value = None; annotation = None }]
else
[]
in
List.concat
[
positional_only_parameters;
delimiter_parameter ~should_insert:(not (List.is_empty positional_only_parameters)) "/";
regular_parameters;
Option.to_list vararg_parameter;
delimiter_parameter
~should_insert:
((not (List.is_empty keyword_only_parameters)) && Option.is_none vararg_parameter)
"*";
keyword_only_parameters;
Option.to_list kwarg_parameter;
]
let expression =
let open Ast.Expression in
let module Node = Ast.Node in
let bool_op ~location ~op ~values =
match values with
| [] ->
(* NOTE(grievejia): I don't think the CPython parser will give us empty boolean operands.
Doing this just to be safe. *)
let default_value =
match op with
| BooleanOperator.And -> Constant.True
| BooleanOperator.Or -> Constant.False
in
Expression.Constant default_value |> Node.create ~location
| [value] -> value
| first :: second :: rest ->
(* Boolean operators are left-associative *)
let init =
Expression.BooleanOperator { BooleanOperator.left = first; operator = op; right = second }
|> Node.create ~location:{ location with stop = second.location.stop }
in
let f sofar next =
Expression.BooleanOperator { BooleanOperator.left = sofar; operator = op; right = next }
|> Node.create ~location:{ location with stop = next.location.stop }
in
List.fold rest ~init ~f
in
let named_expr ~location ~target ~value =
(* TODO(T47589601): `target` can be strenghthened into `Identifier.t` if qualification is
removed. *)
Expression.WalrusOperator { WalrusOperator.target; value } |> Node.create ~location
in
let bin_op ~location ~left ~op ~right =
(* TODO(T101299882): Avoid lowering binary operators in parsing phase. *)
let callee =
let dunder_name = Caml.Format.sprintf "__%s__" op in
Expression.Name
(Name.Attribute { base = left; attribute = identifier dunder_name; special = true })
|> Node.create ~location:(Node.location left)
in
Expression.Call { callee; arguments = [{ Call.Argument.name = None; value = right }] }
|> Node.create ~location
in
let unary_op ~location ~op ~operand =
match op, operand with
| UnaryOperator.Positive, { Node.value = Expression.Constant (Constant.Integer literal); _ } ->
Expression.Constant (Constant.Integer literal) |> Node.create ~location
| UnaryOperator.Negative, { Node.value = Expression.Constant (Constant.Integer literal); _ } ->
Expression.Constant (Constant.Integer (-literal)) |> Node.create ~location
| _ ->
Expression.UnaryOperator { UnaryOperator.operator = op; operand } |> Node.create ~location
in
let lambda ~location ~args ~body =
Expression.Lambda { Lambda.parameters = args; body } |> Node.create ~location
in
let if_exp ~location ~test ~body ~orelse =
Expression.Ternary { Ternary.target = body; test; alternative = orelse }
|> Node.create ~location
in
let dict ~location ~keys ~values =
let entries, keywords =
(* `keys` and `values` are guaranteed by CPython parser to be of the same length. *)
List.zip_exn keys values
|> List.partition_map ~f:(fun (key, value) ->
match key with
| None -> Either.Second value
| Some key -> Either.First { Dictionary.Entry.key; value })
in
Expression.Dictionary { Dictionary.entries; keywords } |> Node.create ~location
in
let set ~location ~elts = Expression.Set elts |> Node.create ~location in
let list_comp ~location ~elt ~generators =
Expression.ListComprehension { Comprehension.element = elt; generators }
|> Node.create ~location
in
let set_comp ~location ~elt ~generators =
Expression.SetComprehension { Comprehension.element = elt; generators } |> Node.create ~location
in
let dict_comp ~location ~key ~value ~generators =
Expression.DictionaryComprehension
{ Comprehension.element = { Dictionary.Entry.key; value }; generators }
|> Node.create ~location
in
let generator_exp ~location ~elt ~generators =
Expression.Generator { Comprehension.element = elt; generators } |> Node.create ~location
in
let await ~location ~value = Expression.Await value |> Node.create ~location in
let yield ~location ~value = Expression.Yield value |> Node.create ~location in
let yield_from ~location ~value = Expression.YieldFrom value |> Node.create ~location in
let compare ~location ~left ~ops ~comparators =
let f (sofar, last) (operator, next) =
(* NOTE(grievejia): This is not 100% accurate since `last` is never evaluated more than once
at runtime. But it's a fairly close approximation. *)
let right =
Expression.ComparisonOperator { ComparisonOperator.left = last; operator; right = next }
|> Node.create
~location:{ Ast.Location.start = last.location.start; stop = next.location.stop }
in
let sofar =
Expression.BooleanOperator
{ BooleanOperator.left = sofar; operator = BooleanOperator.And; right }
|> Node.create ~location:{ location with stop = right.location.stop }
in
sofar, next
in
(* `ops` and `comparators` are guaranteed by CPython parser to be of the same length. *)
List.zip_exn ops comparators
|> function
| [] -> left
| (operator, right) :: rest ->
let first_operand =
Expression.ComparisonOperator { ComparisonOperator.left; operator; right }
|> Node.create ~location:{ location with stop = right.location.stop }
in
let result, _ = List.fold ~init:(first_operand, right) ~f rest in
result
in
let call ~location ~func ~args ~keywords =
let arguments =
(* NOTE(T101305324): Ordering between positional and keyword args is artificial. *)
List.append
(List.map args ~f:convert_positional_argument)
(List.map keywords ~f:convert_keyword_argument)
in
Expression.Call { callee = func; arguments } |> Node.create ~location
in
let formatted_value ~location ~value ~conversion:_ ~format_spec:_ =
Expression.FormatString [Substring.Format value] |> Node.create ~location
in
let joined_str ~location ~values =
let collapse_formatted_value ({ Node.value; location } as expression) =
match value with
| Expression.Constant (Constant.String { StringLiteral.kind = String; value }) ->
Substring.Literal (Node.create ~location value)
| Expression.FormatString [substring] -> substring
| _ ->
(* NOTE (grievejia): It may be impossible for CPython parser to reach this branch *)
Substring.Format expression
in
Expression.FormatString (List.map values ~f:collapse_formatted_value) |> Node.create ~location
in
let constant ~location ~value ~kind:_ = Expression.Constant value |> Node.create ~location in
let attribute ~location ~value ~attr ~ctx:() =
Expression.Name
(Name.Attribute { Name.Attribute.base = value; attribute = attr; special = false })
|> Node.create ~location
in
let subscript ~location ~value ~slice ~ctx:() =
(* TODO(T101303314): We should avoid lowering subscript expressions at parser phase. *)
let callee =
let { Node.location = value_location; _ } = value in
Expression.Name
(Name.Attribute { Name.Attribute.base = value; attribute = "__getitem__"; special = true })
|> Node.create ~location:value_location
in
let arguments = [{ Call.Argument.name = None; value = slice }] in
Expression.Call { callee; arguments } |> Node.create ~location
in
let starred ~location ~value ~ctx:() =
Expression.Starred (Starred.Once value) |> Node.create ~location
in
let name ~location ~id ~ctx:() = Expression.Name (Name.Identifier id) |> Node.create ~location in
let list ~location ~elts ~ctx:() = Expression.List elts |> Node.create ~location in
let tuple ~location ~elts ~ctx:() = Expression.Tuple elts |> Node.create ~location in
let slice ~location ~lower ~upper ~step =
(* TODO(T101302994): We should avoid lowering slice expressions at parser phase. *)
let callee = Expression.Name (Name.Identifier "slice") |> Node.create ~location in
let arguments =
let to_argument = function
| None -> Expression.Constant Constant.NoneLiteral |> Node.create ~location:Ast.Location.any
| Some expression -> expression
in
[
{ Call.Argument.name = None; value = to_argument lower };
{ Call.Argument.name = None; value = to_argument upper };
{ Call.Argument.name = None; value = to_argument step };
]
in
Expression.Call { callee; arguments } |> Node.create ~location
in
PyreAst.TaglessFinal.Expression.make
~bool_op
~named_expr
~bin_op
~unary_op
~lambda
~if_exp
~dict
~set
~list_comp
~set_comp
~dict_comp
~generator_exp
~await
~yield
~yield_from
~compare
~call
~formatted_value
~joined_str
~constant
~attribute
~subscript
~starred
~name
~list
~tuple
~slice
()
module FunctionSignature = struct
type t = {
parameter_annotations: Ast.Expression.t list;
return_annotation: Ast.Expression.t;
}
end
module StatementContext = struct
type t = {
(* [parse_function_signature] takes function type comment as string and parse it into a
[FunctionSignature.t]. *)
parse_function_signature: string -> (FunctionSignature.t, Error.t) Result.t;
(* [parent] holds the name of the immediate containing class of a statement. *)
parent: Ast.Identifier.t option;
}
end
let build_statements ~context statement_builders =
let build_statement builder = builder ~context in
List.concat (List.map statement_builders ~f:build_statement)
let with_item ~context_expr ~optional_vars = context_expr, optional_vars
let import_alias ~location ~name ~asname =
let open Ast in
Node.create ~location { Statement.Import.name = Reference.create name; alias = asname }
let exception_handler ~location:_ ~type_ ~name ~body ~context =
{ Ast.Statement.Try.Handler.kind = type_; name; body = build_statements ~context body }
let build_exception_handlers ~context exception_handler_builders =
let build_exception_handler builder = builder ~context in
List.map exception_handler_builders ~f:build_exception_handler
let match_case ~pattern ~guard ~body ~context =
{ Ast.Statement.Match.Case.pattern; guard; body = build_statements ~context body }
let build_match_cases ~context match_cases =
let build_match_case builder = builder ~context in
List.map match_cases ~f:build_match_case
let pattern =
let open Ast.Expression in
let open Ast.Statement in
let module Node = Ast.Node in
let match_value ~location ~value = Match.Pattern.MatchValue value |> Node.create ~location in
let match_singleton ~location ~value =
Match.Pattern.MatchSingleton value |> Node.create ~location
in
let match_sequence ~location ~patterns =
Match.Pattern.MatchSequence patterns |> Node.create ~location
in
let match_mapping ~location ~keys ~patterns ~rest =
Match.Pattern.MatchMapping { keys; patterns; rest } |> Node.create ~location
in
let match_class ~location ~cls ~patterns ~kwd_attrs ~kwd_patterns =
let class_name =
match Node.value cls with
| Expression.Name name -> Node.create ~location:(Node.location cls) name
| _ ->
let {
Ast.Location.start = { line; column };
stop = { line = end_line; column = end_column };
}
=
location
in
raise
(InternalError
{
Error.line;
column;
end_line;
end_column;
message = "class pattern expects simple identifier or attribute accesses only";
})
in
Match.Pattern.MatchClass
{ class_name; patterns; keyword_attributes = kwd_attrs; keyword_patterns = kwd_patterns }
|> Node.create ~location
in
let match_star ~location ~name = Match.Pattern.MatchStar name |> Node.create ~location in
let match_as ~location ~pattern ~name =
match name, pattern with
| None, Some _ ->
let {
Ast.Location.start = { line; column };
stop = { line = end_line; column = end_column };
}
=
location
in
raise
(InternalError
{
Error.line;
column;
end_line;
end_column;
message = "as pattern expects non-wildcard pattern when name is `_`";
})
| None, None -> Node.create ~location Match.Pattern.MatchWildcard
| Some name, pattern -> Match.Pattern.MatchAs { name; pattern } |> Node.create ~location
in
let match_or ~location ~patterns = Match.Pattern.MatchOr patterns |> Node.create ~location in
PyreAst.TaglessFinal.Pattern.make
~match_value
~match_singleton
~match_sequence
~match_mapping
~match_class
~match_star
~match_as
~match_or
()
let create_assign ~location ~target ~annotation ~value () =
let open Ast.Expression in
let open Ast.Statement in
let module Node = Ast.Node in
let value =
(* TODO(T101298692): Make `value` optional in assign statement and stop auto-filling `...` *)
let location =
let open Ast.Location in
{ location with start = location.stop }
in
Option.value value ~default:(Expression.Constant Constant.Ellipsis |> Node.create ~location)
in
match Node.value target with
| Expression.Call
{
callee =
{
Node.value =
Expression.Name
(Name.Attribute { Name.Attribute.base; attribute = "__getitem__"; special = true });
location = callee_location;
};
arguments;
} ->
let setitem =
Expression.Call
{
callee =
Expression.Name
(Name.Attribute { Name.Attribute.base; attribute = "__setitem__"; special = true })
|> Node.create ~location:callee_location;
arguments = List.append arguments [{ Call.Argument.name = None; value }];
}
|> Node.create ~location
in
Statement.Expression setitem |> Node.create ~location
| _ ->
(* TODO(T101303314): This does not take into account things like `a[0], b = ...`, where we'll
need to turn `a[0]` into `__setitem__` call. *)
Statement.Assign { target; annotation; value } |> Node.create ~location
let process_function_type_comment
~context:{ StatementContext.parse_function_signature; parent }
~parameters
~returns
~comment_location
= function
| None -> Result.Ok (parameters, returns)
| Some type_comment -> (
match parse_function_signature type_comment with
| Result.Error _ -> Result.Error "Syntax error in function signature type comment"
| Result.Ok { FunctionSignature.parameter_annotations; return_annotation } -> (
let open Ast.Expression in
let module Node = Ast.Node in
let parameter_annotations =
let parameter_count = List.length parameters in
match parameter_annotations with
| [{ Node.value = Expression.Constant Constant.Ellipsis; _ }] ->
List.init parameter_count ~f:(fun _ -> None)
| _ ->
let annotations = List.map parameter_annotations ~f:Option.some in
let annotation_count = List.length annotations in
(* For methods, it is allowed to have one extra `self` and `cls` parameter without
annotation. *)
if Option.is_some parent && annotation_count == parameter_count - 1 then
None :: annotations
else
annotations
in
match List.zip parameters parameter_annotations with
| List.Or_unequal_lengths.Unequal_lengths ->
let message =
Format.sprintf
"Function signature type comment has %d parameter types, while the corresponding \
function contains %d parameters"
(List.length parameter_annotations)
(List.length parameters)
in
Result.Error message
| List.Or_unequal_lengths.Ok pairs ->
let location_patcher =
(* NOTE(grievejia): Locations in both `parameter_annotations` and
`return_annotation` are all off since they are counted from the start of
`type_comment`, not from the start of the entire file. Therefore, we need to
replace them with something more sensible. *)
Mapper.create_transformer ~map_location:(fun _ -> comment_location) ()
in
let override_annotation old_annotation new_annotation =
(* NOTE(grievejia): Currently we let inline annotations take precedence over comment
annotations. *)
match old_annotation with
| Some _ -> old_annotation
| None -> (
match new_annotation with
| None -> None
| Some new_annotation ->
Some (Mapper.map ~mapper:location_patcher new_annotation))
in
let override_parameter ({ Node.value = parameter; location }, new_annotation) =
let { Parameter.annotation; _ } = parameter in
{
Node.location;
value =
{ parameter with annotation = override_annotation annotation new_annotation };
}
in
Result.Ok
( List.map pairs ~f:override_parameter,
override_annotation returns (Some return_annotation) )))
let statement =
let open Ast.Expression in
let open Ast.Statement in
let module Node = Ast.Node in
let create_function_definition
~location
~async
~name
~args
~body
~decorator_list
~returns
~type_comment
~context:({ StatementContext.parent; _ } as context)
=
let body = build_statements ~context:{ context with parent = None } body in
let comment_location =
(* NOTE(grievejia): This is just a rough estimation on where type comment is. We don't know
for sure since CPython does not preserve the positions of those comments. *)
let open Ast.Location in
let estimated_stop =
match body with
| [] -> location.stop
| { Node.location; _ } :: _ -> location.start
in
{ start = location.start; stop = estimated_stop }
in
match
process_function_type_comment
~context
~parameters:args
~returns
~comment_location
type_comment
with
| Result.Error message ->
let {
Ast.Location.start = { line; column };
stop = { line = end_line; column = end_column };
}
=
location
in
raise (InternalError { Error.line; column; end_line; end_column; message })
| Result.Ok (parameters, return_annotation) ->
let signature =
{
Define.Signature.name = Ast.Reference.create name;
parameters;
decorators = decorator_list;
return_annotation;
async;
generator = is_generator body;
parent = Option.map parent ~f:Ast.Reference.create;
nesting_define = None;
}
in
[
Statement.Define { Define.signature; captures = []; unbound_names = []; body }
|> Node.create ~location;
]
in
let function_def ~location ~name ~args ~body ~decorator_list ~returns ~type_comment ~context =
create_function_definition
~location
~async:false
~name
~args
~body
~decorator_list
~returns
~type_comment
~context
in
let async_function_def ~location ~name ~args ~body ~decorator_list ~returns ~type_comment ~context
=
create_function_definition
~location
~async:true
~name
~args
~body
~decorator_list
~returns
~type_comment
~context
in
let class_def ~location ~name ~bases ~keywords ~body ~decorator_list ~context =
let base_arguments =
List.append
(List.map bases ~f:convert_positional_argument)
(List.map keywords ~f:convert_keyword_argument)
in
[
Statement.Class
{
Class.name = Ast.Reference.create name;
base_arguments;
body = build_statements ~context:{ context with StatementContext.parent = Some name } body;
decorators = decorator_list;
top_level_unbound_names = [];
}
|> Node.create ~location;
]
in
let return ~location ~value ~context:_ =
[Statement.Return { Return.expression = value; is_implicit = false } |> Node.create ~location]
in
let delete ~location ~targets ~context:_ = [Statement.Delete targets |> Node.create ~location] in
let assign ~location ~targets ~value ~type_comment ~context:_ =
(* Eagerly turn chained assignments `a = b = c` into `a = c; b = c`. *)
let create_assign_for_target target =
let location =
let open Ast.Location in
let { start; _ } = Node.location target in
{ location with start }
in
let annotation =
match type_comment, target with
| Some comment, { Node.value = Expression.Name _; _ } ->
let annotation = Expression.Constant (Constant.String (StringLiteral.create comment)) in
let location =
(* Type comments do not have locations attached in CPython. This is just a rough
guess.*)
let open Ast.Location in
let { stop = { line = start_line; column = start_column }; _ } =
Node.location value
in
let { stop; _ } = location in
{ start = { line = start_line; column = start_column + 1 }; stop }
in
Some (Node.create ~location annotation)
| _ ->
(* TODO (T104971233): Support type comments when the LHS of assign is a list/tuple. *)
None
in
create_assign ~location ~target ~annotation ~value:(Some value) ()
in
List.map targets ~f:create_assign_for_target
in
let aug_assign ~location ~target ~op ~value ~context:_ =
let callee =
let dunder_name = Caml.Format.sprintf "__i%s__" op in
Expression.Name
(Name.Attribute { base = target; attribute = identifier dunder_name; special = true })
|> Node.create ~location:(Node.location target)
in
let value =
Expression.Call { callee; arguments = [{ Call.Argument.name = None; value }] }
|> Node.create ~location
in
[create_assign ~location ~target ~annotation:None ~value:(Some value) ()]
in
let ann_assign ~location ~target ~annotation ~value ~simple:_ ~context:_ =
[create_assign ~location ~target ~annotation:(Some annotation) ~value ()]
in
let for_ ~location ~target ~iter ~body ~orelse ~type_comment:_ ~context =
[
Statement.For
{
For.target;
iterator = iter;
body = build_statements ~context body;
orelse = build_statements ~context orelse;
async = false;
}
|> Node.create ~location;
]
in
let async_for ~location ~target ~iter ~body ~orelse ~type_comment:_ ~context =
[
Statement.For
{
For.target;
iterator = iter;
body = build_statements ~context body;
orelse = build_statements ~context orelse;
async = true;
}
|> Node.create ~location;
]
in
let while_ ~location ~test ~body ~orelse ~context =
[
Statement.While
{
While.test;
body = build_statements ~context body;
orelse = build_statements ~context orelse;
}
|> Node.create ~location;
]
in
let if_ ~location ~test ~body ~orelse ~context =
[
Statement.If
{
If.test;
body = build_statements ~context body;
orelse = build_statements ~context orelse;
}
|> Node.create ~location;
]
in
let with_ ~location ~items ~body ~type_comment:_ ~context =
[
Statement.With { With.items; body = build_statements ~context body; async = false }
|> Node.create ~location;
]
in
let async_with ~location ~items ~body ~type_comment:_ ~context =
[
Statement.With { With.items; body = build_statements ~context body; async = true }
|> Node.create ~location;
]
in
let match_ ~location ~subject ~cases ~context =
let check_cases_refutability cases =
let is_case_irrefutable case = not (Ast.Statement.Match.Case.is_refutable case) in
let raise_remanining_patterns_unreachable { Match.Case.pattern = { Ast.Node.location; _ }; _ }
=
let {
Ast.Location.start = { line; column };
stop = { line = end_line; column = end_column };
}
=
location
in
raise
(InternalError
{
Error.line;
column;
end_line;
end_column;
message = "This pattern makes remaining patterns unreachable.";
})
in
List.iter (List.drop_last_exn cases) ~f:(fun case ->
if is_case_irrefutable case then raise_remanining_patterns_unreachable case)
in
let cases = build_match_cases ~context cases in
check_cases_refutability cases;
[Statement.Match { Match.subject; cases } |> Node.create ~location]
in
let raise_ ~location ~exc ~cause ~context:_ =
[Statement.Raise { Raise.expression = exc; from = cause } |> Node.create ~location]
in
let try_ ~location ~body ~handlers ~orelse ~finalbody ~context =
[
Statement.Try
{
Try.body = build_statements ~context body;
orelse = build_statements ~context orelse;
finally = build_statements ~context finalbody;
handlers = build_exception_handlers ~context handlers;
}
|> Node.create ~location;
]
in
let assert_ ~location ~test ~msg ~context:_ =
[
Statement.Assert { Assert.test; message = msg; origin = Assert.Origin.Assertion }
|> Node.create ~location;
]
in
let import ~location ~names ~context:_ =
[Statement.Import { Import.imports = names; from = None } |> Node.create ~location]
in
let import_from ~location ~module_ ~names ~level ~context:_ =
let dots = List.init level ~f:(fun _ -> ".") |> String.concat ~sep:"" in
let from_module_name = Option.value module_ ~default:"" in
let from = Caml.Format.sprintf "%s%s" dots from_module_name |> Ast.Reference.create in
[Statement.Import { Import.imports = names; from = Some from } |> Node.create ~location]
in
let global ~location ~names ~context:_ = [Statement.Global names |> Node.create ~location] in
let nonlocal ~location ~names ~context:_ = [Statement.Nonlocal names |> Node.create ~location] in
let expr ~location ~value ~context:_ = [Statement.Expression value |> Node.create ~location] in
let pass ~location ~context:_ = [Statement.Pass |> Node.create ~location] in
let break ~location ~context:_ = [Statement.Break |> Node.create ~location] in
let continue ~location ~context:_ = [Statement.Continue |> Node.create ~location] in
PyreAst.TaglessFinal.Statement.make
~function_def
~async_function_def
~class_def
~return
~delete
~assign
~aug_assign
~ann_assign
~for_
~async_for
~while_
~if_
~with_
~async_with
~match_
~raise_
~try_
~assert_
~import
~import_from
~global
~nonlocal
~expr
~pass
~break
~continue
()
let type_ignore ~lineno:_ ~tag:_ = ()
let module_ ~body ~type_ignores:_ ~context = build_statements ~context body
let function_type ~argtypes ~returns =
{ FunctionSignature.parameter_annotations = argtypes; return_annotation = returns }
let specification =
PyreAst.TaglessFinal.make
~argument
~arguments
~binary_operator
~boolean_operator
~comparison_operator
~comprehension
~constant
~exception_handler
~expression
~expression_context
~function_type
~identifier
~import_alias
~keyword
~location
~match_case
~module_
~pattern
~position
~statement
~type_ignore
~unary_operator
~with_item
()
let with_context ?on_failure = PyreAst.Parser.with_context ?on_init_failure:on_failure
let parse_module ?enable_type_comment ~context text =
try
let open Result in
let parse_function_signature text =
PyreAst.Parser.TaglessFinal.parse_function_type ~context ~spec:specification text
in
PyreAst.Parser.TaglessFinal.parse_module ?enable_type_comment ~context ~spec:specification text
>>= fun module_builder ->
Ok (module_builder ~context:{ StatementContext.parse_function_signature; parent = None })
with
| InternalError error -> Result.Error error
let parse_module_exn ?enable_type_comment ~context text =
match parse_module ?enable_type_comment ~context text with
| Result.Ok statements -> statements
| Result.Error error -> raise (Exception error)
let parse_expression ~context text =
try PyreAst.Parser.TaglessFinal.parse_expression ~context ~spec:specification text with
| InternalError error -> Result.Error error