source/ast/transform.ml (538 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 Expression
open Statement
module type Transformer = sig
type t
val transform_expression_children : t -> Expression.t -> bool
val expression : t -> Expression.t -> Expression.t
val transform_children : t -> Statement.t -> t * bool
val statement : t -> Statement.t -> t * Statement.t list
end
module type StatementTransformer = sig
type t
val statement : t -> Statement.t -> t * Statement.t list
end
module Identity : sig
val transform_expression_children : 't -> Expression.t -> bool
val expression : 't -> Expression.t -> Expression.t
val transform_children : 't -> Statement.t -> 't * bool
val statement : 't -> Statement.t -> 't * Statement.t list
end = struct
let transform_expression_children _ _ = true
let expression _ expression = expression
let transform_children state _ = state, true
let statement state statement = state, [statement]
end
module Make (Transformer : Transformer) = struct
type result = {
state: Transformer.t;
source: Source.t;
}
let source { source; _ } = source
let transform state source =
let state = ref state in
let transform_list list ~f =
let accumulate list element = f element :: list in
List.fold_left list ~f:accumulate ~init:[] |> List.rev
in
let transform_argument { Call.Argument.name; value } ~transform_expression =
{ Call.Argument.name; value = transform_expression value }
in
let transform_parameter
({ Node.value = { Parameter.name; value; annotation }; _ } as node)
~transform_expression
=
{
node with
Node.value =
{
Parameter.name;
value = value >>| transform_expression;
annotation = annotation >>| transform_expression;
};
}
in
let transform_generator
{ Comprehension.Generator.target; iterator; conditions; async }
~transform_expression
=
{
Comprehension.Generator.target = transform_expression target;
iterator = transform_expression iterator;
conditions = transform_list conditions ~f:transform_expression;
async;
}
in
let transform_entry { Dictionary.Entry.key; value } ~transform_expression =
{ Dictionary.Entry.key = transform_expression key; value = transform_expression value }
in
let transform_substring substring ~transform_expression =
match substring with
| Substring.Format expression -> Substring.Format (transform_expression expression)
| Substring.Literal _ -> substring
in
let rec transform_expression expression =
let transform_children value =
match value with
| Expression.Await expression -> Expression.Await (transform_expression expression)
| BooleanOperator { BooleanOperator.left; operator; right } ->
BooleanOperator
{
BooleanOperator.left = transform_expression left;
operator;
right = transform_expression right;
}
| Call { callee; arguments } ->
Call { callee = transform_expression callee; arguments = transform_arguments arguments }
| ComparisonOperator { ComparisonOperator.left; operator; right } ->
ComparisonOperator
{
ComparisonOperator.left = transform_expression left;
operator;
right = transform_expression right;
}
| Constant _ -> value
| Dictionary { Dictionary.entries; keywords } ->
Dictionary
{
Dictionary.entries =
transform_list entries ~f:(transform_entry ~transform_expression);
keywords = transform_list keywords ~f:transform_expression;
}
| DictionaryComprehension { Comprehension.element; generators } ->
DictionaryComprehension
{
Comprehension.element = transform_entry element ~transform_expression;
generators =
transform_list generators ~f:(transform_generator ~transform_expression);
}
| Generator { Comprehension.element; generators } ->
Generator
{
Comprehension.element = transform_expression element;
generators =
transform_list generators ~f:(transform_generator ~transform_expression);
}
| FormatString substrings ->
FormatString (transform_list substrings ~f:(transform_substring ~transform_expression))
| Lambda { Lambda.parameters; body } ->
Lambda
{
Lambda.parameters =
transform_list parameters ~f:(transform_parameter ~transform_expression);
body = transform_expression body;
}
| List elements -> List (transform_list elements ~f:transform_expression)
| ListComprehension { Comprehension.element; generators } ->
ListComprehension
{
Comprehension.element = transform_expression element;
generators =
transform_list generators ~f:(transform_generator ~transform_expression);
}
| Name (Name.Identifier _) -> value
| Name (Name.Attribute ({ base; _ } as name)) ->
Name (Name.Attribute { name with base = transform_expression base })
| Set elements -> Set (transform_list elements ~f:transform_expression)
| SetComprehension { Comprehension.element; generators } ->
SetComprehension
{
Comprehension.element = transform_expression element;
generators =
transform_list generators ~f:(transform_generator ~transform_expression);
}
| Starred starred ->
let starred =
match starred with
| Starred.Once expression -> Starred.Once (transform_expression expression)
| Starred.Twice expression -> Starred.Twice (transform_expression expression)
in
Starred starred
| Ternary { Ternary.target; test; alternative } ->
Ternary
{
Ternary.target = transform_expression target;
test = transform_expression test;
alternative = transform_expression alternative;
}
| Tuple elements -> Tuple (transform_list elements ~f:transform_expression)
| UnaryOperator { UnaryOperator.operator; operand } ->
UnaryOperator { UnaryOperator.operator; operand = transform_expression operand }
| WalrusOperator { target; value } ->
WalrusOperator
{ target = transform_expression target; value = transform_expression value }
| Expression.Yield expression -> Expression.Yield (expression >>| transform_expression)
| Expression.YieldFrom expression ->
Expression.YieldFrom (expression |> transform_expression)
in
let initial_state = !state in
let expression =
if Transformer.transform_expression_children !state expression then
{ expression with Node.value = transform_children (Node.value expression) }
else
expression
in
let expression = Transformer.expression !state expression in
state := initial_state;
expression
and transform_arguments arguments =
let transform_argument { Call.Argument.name; value } =
{ Call.Argument.name; value = transform_expression value }
in
transform_list arguments ~f:transform_argument
in
let rec transform_statement statement =
let transform_children value =
match value with
| Statement.Assign { Assign.target; annotation; value } ->
Statement.Assign
{
Assign.target = transform_expression target;
annotation = annotation >>| transform_expression;
value = transform_expression value;
}
| Assert { Assert.test; message; origin } ->
Assert
{
Assert.test = transform_expression test;
message = message >>| transform_expression;
origin;
}
| Break -> value
| Class { Class.name; base_arguments; body; decorators; top_level_unbound_names } ->
Class
{
Class.name;
base_arguments =
transform_list base_arguments ~f:(transform_argument ~transform_expression);
body = transform_list body ~f:transform_statement |> List.concat;
decorators = transform_list decorators ~f:transform_expression;
top_level_unbound_names;
}
| Continue -> value
| Define { signature; captures; unbound_names; body } ->
let transform_signature
{
Define.Signature.name;
parameters;
decorators;
return_annotation;
async;
parent;
nesting_define;
generator;
}
=
{
Define.Signature.name;
parameters =
transform_list parameters ~f:(transform_parameter ~transform_expression);
decorators = transform_list decorators ~f:transform_expression;
return_annotation = return_annotation >>| transform_expression;
async;
parent;
nesting_define;
generator;
}
in
let transform_capture { Define.Capture.name; kind } =
let transform_kind = function
| Define.Capture.Kind.Annotation annotation ->
let annotation = Option.map annotation ~f:transform_expression in
Define.Capture.Kind.Annotation annotation
| Define.Capture.Kind.DefineSignature value ->
let value = transform_signature value in
Define.Capture.Kind.DefineSignature value
| Define.Capture.Kind.(Self _ | ClassSelf _) as kind -> kind
in
{ Define.Capture.name; kind = transform_kind kind }
in
Define
{
signature = transform_signature signature;
captures = List.map captures ~f:transform_capture;
unbound_names;
body = transform_list body ~f:transform_statement |> List.concat;
}
| Delete expressions -> Delete (List.map expressions ~f:transform_expression)
| Expression expression -> Expression (transform_expression expression)
| For { For.target; iterator; body; orelse; async } ->
For
{
For.target = transform_expression target;
iterator = transform_expression iterator;
body = transform_list body ~f:transform_statement |> List.concat;
orelse = transform_list orelse ~f:transform_statement |> List.concat;
async;
}
| Global _ -> value
| If { If.test; body; orelse } ->
If
{
If.test = transform_expression test;
body = transform_list body ~f:transform_statement |> List.concat;
orelse = transform_list orelse ~f:transform_statement |> List.concat;
}
| Import _ -> value
| Match { Match.subject; cases } ->
let rec transform_pattern { Node.value; location } =
let value =
match value with
| Match.Pattern.MatchAs { pattern; name } ->
Match.Pattern.MatchAs { pattern = pattern >>| transform_pattern; name }
| MatchClass { class_name; patterns; keyword_attributes; keyword_patterns } ->
MatchClass
{
class_name;
patterns = transform_list patterns ~f:transform_pattern;
keyword_attributes;
keyword_patterns = transform_list keyword_patterns ~f:transform_pattern;
}
| MatchMapping { keys; patterns; rest } ->
MatchMapping
{
keys = transform_list keys ~f:transform_expression;
patterns = transform_list patterns ~f:transform_pattern;
rest;
}
| MatchOr patterns -> MatchOr (transform_list patterns ~f:transform_pattern)
| MatchSequence patterns ->
MatchSequence (transform_list patterns ~f:transform_pattern)
| MatchSingleton constant -> (
let expression =
transform_expression { Node.value = Expression.Constant constant; location }
in
match expression.value with
| Expression.Constant constant -> MatchSingleton constant
| _ -> MatchValue expression)
| MatchStar maybe_identifier -> MatchStar maybe_identifier
| MatchValue expression -> MatchValue expression
| MatchWildcard -> MatchWildcard
in
{ Node.value; location }
in
let transform_case { Match.Case.pattern; guard; body } =
{
Match.Case.pattern = transform_pattern pattern;
guard = guard >>| transform_expression;
body = transform_list body ~f:transform_statement |> List.concat;
}
in
Match
{
Match.subject = transform_expression subject;
cases = transform_list cases ~f:transform_case;
}
| Nonlocal _ -> value
| Pass -> value
| Raise { Raise.expression; from } ->
Raise
{
Raise.expression = expression >>| transform_expression;
from = from >>| transform_expression;
}
| Return ({ Return.expression; _ } as return) ->
Return { return with Return.expression = expression >>| transform_expression }
| Try { Try.body; handlers; orelse; finally } ->
let transform_handler { Try.Handler.kind; name; body } =
{
Try.Handler.kind = kind >>| transform_expression;
name;
body = transform_list body ~f:transform_statement |> List.concat;
}
in
(* Body needs to be evaluated first to update local scope *)
let body = transform_list body ~f:transform_statement |> List.concat in
let handlers = transform_list handlers ~f:transform_handler in
let orelse = transform_list orelse ~f:transform_statement |> List.concat in
let finally = transform_list finally ~f:transform_statement |> List.concat in
Try { Try.body; handlers; orelse; finally }
| With { With.items; body; async } ->
let transform_item (item, alias) =
transform_expression item, alias >>| transform_expression
in
With
{
With.items = transform_list items ~f:transform_item;
body = transform_list body ~f:transform_statement |> List.concat;
async;
}
| While { While.test; body; orelse } ->
While
{
While.test = transform_expression test;
body = transform_list body ~f:transform_statement |> List.concat;
orelse = transform_list orelse ~f:transform_statement |> List.concat;
}
in
let statement =
let parent_state, should_transform_children =
Transformer.transform_children !state statement
in
if should_transform_children then (
state := parent_state;
{ statement with Node.value = transform_children (Node.value statement) })
else
statement
in
let new_state, statements = Transformer.statement !state statement in
state := new_state;
statements
in
let statements =
transform_list source.Source.statements ~f:transform_statement |> List.concat
in
{ state = !state; source = { source with Source.statements } }
end
module MakeStatementTransformer (Transformer : StatementTransformer) = struct
type result = {
state: Transformer.t;
source: Source.t;
}
let source { source; _ } = source
let transform state source =
let state = ref state in
let open Statement in
let rec transform_statement { Node.location; value } =
let value =
match value with
| Assign _
| Assert _
| Break
| Continue
| Delete _
| Expression _
| Global _
| Import _
| Pass
| Raise _
| Return _
| Nonlocal _ ->
value
| Class ({ Class.body; _ } as value) ->
Class { value with Class.body = List.concat_map ~f:transform_statement body }
| Define ({ Define.body; _ } as value) ->
Define { value with Define.body = List.concat_map ~f:transform_statement body }
| With ({ With.body; _ } as value) ->
With { value with With.body = List.concat_map ~f:transform_statement body }
| For ({ For.body; orelse; _ } as value) ->
let body = List.concat_map ~f:transform_statement body in
let orelse = List.concat_map ~f:transform_statement orelse in
For { value with For.body; orelse }
| If ({ If.body; orelse; _ } as value) ->
let body = List.concat_map ~f:transform_statement body in
let orelse = List.concat_map ~f:transform_statement orelse in
If { value with If.body; orelse }
| Match ({ Match.cases; _ } as value) ->
let transform_case ({ Match.Case.body; _ } as value) =
{ value with Match.Case.body = List.concat_map ~f:transform_statement body }
in
Match { value with Match.cases = List.map ~f:transform_case cases }
| While ({ While.body; orelse; _ } as value) ->
let body = List.concat_map ~f:transform_statement body in
let orelse = List.concat_map ~f:transform_statement orelse in
While { value with While.body; orelse }
| Try { Try.body; handlers; orelse; finally } ->
let transform_handler ({ Try.Handler.body; _ } as value) =
{ value with Try.Handler.body = List.concat_map ~f:transform_statement body }
in
let body = List.concat_map ~f:transform_statement body in
let handlers = List.map ~f:transform_handler handlers in
let orelse = List.concat_map ~f:transform_statement orelse in
let finally = List.concat_map ~f:transform_statement finally in
Try { Try.body; handlers; orelse; finally }
in
let new_state, statements = Transformer.statement !state { Node.location; value } in
state := new_state;
statements
in
let statements = List.concat_map ~f:transform_statement source.Source.statements in
{ state = !state; source = { source with Source.statements } }
end
let transform_expressions ~transform statement =
let module TransformExpressions = Make (struct
type t = unit
let transform_expression_children _ _ = true
let expression _ { Node.value; location } = { Node.value = transform value; location }
let transform_children state _ = state, true
let statement state statement = state, [statement]
end)
in
TransformExpressions.transform () (Source.create [Node.create_with_default_location statement])
|> (fun { TransformExpressions.source; _ } -> source)
|> Source.statements
|> function
| [{ Node.value = statement; _ }] -> statement
| _ -> failwith "expected single statement"
let sanitize_expression expression =
let transform = function
| Expression.Name (Name.Identifier identifier) ->
Expression.Name (Name.Identifier (Identifier.sanitized identifier))
| Name (Name.Attribute ({ attribute; _ } as attribute_expression)) ->
Name
(Name.Attribute { attribute_expression with attribute = Identifier.sanitized attribute })
| expression -> expression
in
match transform_expressions ~transform (Statement.Expression expression) with
| Statement.Expression expression -> expression
| _ -> expression
let sanitize_statement statement =
(* The names in the function signatures are not strictly "expressions", so [transform_expressions]
doesn't transform them. We have to transform them in a separate pass. *)
let module SanitizeSignatures = MakeStatementTransformer (struct
type t = unit
let statement state = function
| {
Node.value =
Statement.Define
({ Define.signature = { Define.Signature.name; parameters; _ } as signature; _ } as
define);
_;
} as statement ->
let transform_parameter
({ Node.value = { Parameter.name; _ } as parameter; _ } as parameter_node)
=
{ parameter_node with value = { parameter with name = Identifier.sanitized name } }
in
( state,
[
{
statement with
value =
Statement.Define
{
define with
signature =
{
signature with
name = Reference.sanitized name;
parameters = List.map parameters ~f:transform_parameter;
};
};
};
] )
| statement -> state, [statement]
end)
in
let sanitized_statement =
let sanitize_expression expression =
Node.create_with_default_location expression |> sanitize_expression |> Node.value
in
let sanitize_signatures statement =
SanitizeSignatures.transform () (Source.create [Node.create_with_default_location statement])
in
transform_expressions ~transform:sanitize_expression statement
|> sanitize_signatures
|> SanitizeSignatures.source
|> Source.statements
in
match sanitized_statement with
| [{ Node.value = statement; _ }] -> statement
| _ -> statement