in hphp/hack/src/parser/lowerer/desugar_expression_tree.rs [647:1441]
fn rewrite_expr(
temps: &mut Temporaries,
e: Expr,
visitor_name: &str,
errors: &mut Vec<(Pos, String)>,
) -> RewriteResult {
use aast::Expr_::*;
// If we can't rewrite the expression (e.g. due to unsupported syntax), return the
// original syntax unmodified. This is particularly useful during code completion,
// where an unfinished code fragment might accidentally use unsupported syntax.
let unchanged_result = RewriteResult {
virtual_expr: e.clone(),
desugar_expr: e.clone(),
};
let Expr(_, pos, expr_) = e;
let pos_expr = exprpos(&pos);
match expr_ {
// Source: MyDsl`1`
// Virtualized: MyDsl::intType()
// Desugared: $0v->visitInt(new ExprPos(...), 1)
Int(_) => {
let virtual_expr = static_meth_call(visitor_name, et::INT_TYPE, vec![], &pos);
let desugar_expr = v_meth_call(
et::VISIT_INT,
vec![pos_expr, Expr((), pos.clone(), expr_)],
&pos,
);
RewriteResult {
virtual_expr,
desugar_expr,
}
}
// Source: MyDsl`1.0`
// Virtualized: MyDsl::floatType()
// Desugared: $0v->visitFloat(new ExprPos(...), 1.0)
Float(_) => {
let virtual_expr = static_meth_call(visitor_name, et::FLOAT_TYPE, vec![], &pos);
let desugar_expr = v_meth_call(
et::VISIT_FLOAT,
vec![pos_expr, Expr((), pos.clone(), expr_)],
&pos,
);
RewriteResult {
virtual_expr,
desugar_expr,
}
}
// Source: MyDsl`'foo'`
// Virtualized: MyDsl::stringType()
// Desugared: $0v->visitString(new ExprPos(...), 'foo')
String(_) => {
let virtual_expr = static_meth_call(visitor_name, et::STRING_TYPE, vec![], &pos);
let desugar_expr = v_meth_call(
et::VISIT_STRING,
vec![pos_expr, Expr((), pos.clone(), expr_)],
&pos,
);
RewriteResult {
virtual_expr,
desugar_expr,
}
}
// Source: MyDsl`true`
// Virtualized: MyDsl::boolType()
// Desugared: $0v->visitBool(new ExprPos(...), true)
True | False => {
let virtual_expr = static_meth_call(visitor_name, et::BOOL_TYPE, vec![], &pos);
let desugar_expr = v_meth_call(
et::VISIT_BOOL,
vec![pos_expr, Expr((), pos.clone(), expr_)],
&pos,
);
RewriteResult {
virtual_expr,
desugar_expr,
}
}
// Source: MyDsl`null`
// Virtualized: MyDsl::nullType()
// Desugared: $0v->visitNull(new ExprPos(...))
Null => {
let virtual_expr = static_meth_call(visitor_name, et::NULL_TYPE, vec![], &pos);
let desugar_expr = v_meth_call(et::VISIT_NULL, vec![pos_expr], &pos);
RewriteResult {
virtual_expr,
desugar_expr,
}
}
// Source: MyDsl`$x`
// Virtualized: $x
// Desugared: $0v->visitLocal(new ExprPos(...), '$x')
Lvar(lid) => {
let desugar_expr = v_meth_call(
et::VISIT_LOCAL,
vec![pos_expr, string_literal(lid.0.clone(), &((lid.1).1))],
&pos,
);
let virtual_expr = Expr((), pos, Lvar(lid));
RewriteResult {
virtual_expr,
desugar_expr,
}
}
Binop(bop) => {
let (op, lhs, rhs) = *bop;
let rewritten_lhs = rewrite_expr(temps, lhs, visitor_name, errors);
let rewritten_rhs = rewrite_expr(temps, rhs, visitor_name, errors);
if op == Bop::Eq(None) {
// Source: MyDsl`$x = ...`
// Virtualized: $x = ...
// Desugared: $0v->visitAssign(new ExprPos(...), $0v->visitLocal(...), ...)
let desugar_expr = v_meth_call(
et::VISIT_ASSIGN,
vec![
pos_expr,
rewritten_lhs.desugar_expr,
rewritten_rhs.desugar_expr,
],
&pos,
);
let virtual_expr = Expr(
(),
pos,
Binop(Box::new((
op,
rewritten_lhs.virtual_expr,
rewritten_rhs.virtual_expr,
))),
);
RewriteResult {
virtual_expr,
desugar_expr,
}
} else {
// Source: MyDsl`... + ...`
// Virtualized: ...->__plus(...)
// Desugared: $0v->visitBinop(new ExprPos(...), ..., '__plus', ...)
let binop_str = match op {
Bop::Plus => "__plus",
Bop::Minus => "__minus",
Bop::Star => "__star",
Bop::Slash => "__slash",
Bop::Percent => "__percent",
// Convert boolean &&, ||
Bop::Ampamp => "__ampamp",
Bop::Barbar => "__barbar",
// Convert comparison operators, <, <=, >, >=, ===, !==
Bop::Lt => "__lessThan",
Bop::Lte => "__lessThanEqual",
Bop::Gt => "__greaterThan",
Bop::Gte => "__greaterThanEqual",
Bop::Eqeqeq => "__tripleEquals",
Bop::Diff2 => "__notTripleEquals",
// Convert string concatenation
Bop::Dot => "__dot",
// Convert bitwise operators, &, |, ^, <<, >>
Bop::Amp => "__amp",
Bop::Bar => "__bar",
Bop::Xor => "__caret",
Bop::Ltlt => "__lessThanLessThan",
Bop::Gtgt => "__greaterThanGreaterThan",
// Explicit list of unsupported operators and error messages
Bop::Starstar => {
errors.push((
pos.clone(),
"Expression trees do not support the exponent operator `**`.".into(),
));
"__unsupported"
}
Bop::Eqeq | Bop::Diff => {
errors.push((
pos.clone(),
"Expression trees only support strict equality operators `===` and `!==`".into(),
));
"__unsupported"
}
Bop::Cmp => {
errors.push((
pos.clone(),
"Expression trees do not support the spaceship operator `<=>`. Try comparison operators like `<` and `>=`".into(),
));
"__unsupported"
}
Bop::QuestionQuestion => {
errors.push((
pos.clone(),
"Expression trees do not support the null coalesce operator `??`."
.into(),
));
"__unsupported"
}
Bop::Eq(_) => {
errors.push((
pos.clone(),
"Expression trees do not support compound assignments. Try the long form style `$foo = $foo + $bar` instead.".into(),
));
"__unsupported"
}
};
let virtual_expr = meth_call(
rewritten_lhs.virtual_expr,
binop_str,
vec![rewritten_rhs.virtual_expr],
&pos,
);
let desugar_expr = v_meth_call(
et::VISIT_BINOP,
vec![
pos_expr,
rewritten_lhs.desugar_expr,
string_literal(pos.clone(), binop_str),
rewritten_rhs.desugar_expr,
],
&pos,
);
RewriteResult {
virtual_expr,
desugar_expr,
}
}
}
// Source: MyDsl`!...`
// Virtualized: ...->__exclamationMark(...)
// Desugared: $0v->visitUnop(new ExprPos(...), ..., '__exclamationMark')
Unop(unop) => {
let (op, operand) = *unop;
let rewritten_operand = rewrite_expr(temps, operand, visitor_name, errors);
let op_str = match op {
// Allow boolean not operator !$x
Uop::Unot => "__exclamationMark",
// Allow negation -$x (required for supporting negative literals -123)
Uop::Uminus => "__negate",
// Allow bitwise complement
Uop::Utild => "__tilde",
// Currently not allowed operators
Uop::Uplus => {
errors.push((
pos.clone(),
"Expression trees do not support the unary plus operator.".into(),
));
"__unsupported"
}
// Postfix ++
Uop::Upincr => "__postfixPlusPlus",
// Prefix ++
Uop::Uincr => {
errors.push((
pos.clone(),
"Expression trees only support postfix increment operator `$x++`.".into(),
));
"__unsupported"
}
// Postfix --
Uop::Updecr => "__postfixMinusMinus",
// Prefix --
Uop::Udecr => {
errors.push((
pos.clone(),
"Expression trees only support postfix decrement operator `$x--`.".into(),
));
"__unsupported"
}
Uop::Usilence => {
errors.push((
pos.clone(),
"Expression trees do not support the error suppression operator `@`."
.into(),
));
"__unsupported"
}
};
let virtual_expr = meth_call(rewritten_operand.virtual_expr, op_str, vec![], &pos);
let desugar_expr = v_meth_call(
et::VISIT_UNOP,
vec![
pos_expr,
rewritten_operand.desugar_expr,
string_literal(pos.clone(), op_str),
],
&pos,
);
RewriteResult {
virtual_expr,
desugar_expr,
}
}
// Source: MyDsl`... ? ... : ...`
// Virtualized: ...->__bool() ? ... : ...
// Desugared: $0v->visitTernary(new ExprPos(...), ..., ..., ...)
Eif(eif) => {
let (e1, e2o, e3) = *eif;
let rewritten_e1 = rewrite_expr(temps, e1, visitor_name, errors);
let rewritten_e2 = if let Some(e2) = e2o {
rewrite_expr(temps, e2, visitor_name, errors)
} else {
errors.push((
pos.clone(),
"Unsupported expression tree syntax: Elvis operator".into(),
));
unchanged_result
};
let rewritten_e3 = rewrite_expr(temps, e3, visitor_name, errors);
let desugar_expr = v_meth_call(
et::VISIT_TERNARY,
vec![
pos_expr,
rewritten_e1.desugar_expr,
rewritten_e2.desugar_expr,
rewritten_e3.desugar_expr,
],
&pos,
);
let virtual_expr = Expr(
(),
pos,
Eif(Box::new((
boolify(rewritten_e1.virtual_expr),
Some(rewritten_e2.virtual_expr),
rewritten_e3.virtual_expr,
))),
);
RewriteResult {
virtual_expr,
desugar_expr,
}
}
// Source: MyDsl`...()`
// Virtualized: ...()
// Desugared: $0v->visitCall(new ExprPos(...), ..., vec[])
Call(call) => {
let (recv, targs, args, variadic) = *call;
if variadic.is_some() {
errors.push((
pos.clone(),
"Expression trees do not support variadic calls.".into(),
));
}
if !targs.is_empty() {
errors.push((
pos.clone(),
"Expression trees do not support function calls with generics.".into(),
));
}
match &recv.2 {
// Don't transform calls to `hh_show`.
Id(sid) if is_typechecker_fun_name(&sid.1) => {
let call_e = Expr::new((), pos, Call(Box::new((recv, targs, args, variadic))));
return RewriteResult {
desugar_expr: call_e.clone(),
virtual_expr: call_e,
};
}
_ => {}
}
let mut args_without_inout = vec![];
for arg in args {
match arg {
(ParamKind::Pnormal, e) => args_without_inout.push(e),
(ParamKind::Pinout(_), Expr(_, p, _)) => errors.push((
p,
"Expression trees do not support `inout` function calls.".into(),
)),
}
}
let (virtual_args, desugar_args) =
rewrite_exprs(temps, args_without_inout, visitor_name, errors);
match recv.2 {
// Source: MyDsl`foo()`
// Virtualized: MyDsl::symbolType($0fpXX)()
// Desugared: $0v->visitCall(new ExprPos(...), $0v->visitGlobalFunction(new ExprPos(...), $0fpXX), vec[])
Id(sid) => {
let len = temps.global_function_pointers.len();
temps.global_function_pointers.push(global_func_ptr(&sid));
let temp_variable = temp_function_pointer_lvar(&recv.1, len);
let desugar_expr = v_meth_call(
et::VISIT_CALL,
vec![
pos_expr.clone(),
v_meth_call(
et::VISIT_GLOBAL_FUNCTION,
vec![pos_expr, temp_variable.clone()],
&pos,
),
vec_literal(desugar_args),
],
&pos,
);
let virtual_expr = Expr(
(),
pos.clone(),
Call(Box::new((
static_meth_call(
visitor_name,
et::SYMBOL_TYPE,
vec![temp_variable],
&pos,
),
vec![],
build_args(virtual_args),
None,
))),
);
RewriteResult {
virtual_expr,
desugar_expr,
}
}
// Source: MyDsl`Foo::bar()`
// Virtualized: MyDsl::symbolType($0smXX)()
// Desugared: $0v->visitCall(new ExprPos(...), $0v->visitStaticMethod(new ExprPos(...), $0smXX, vec[])
ClassConst(cc) => {
let (cid, s) = *cc;
if let ClassId_::CIexpr(Expr(_, _, Id(sid))) = &cid.2 {
if sid.1 == classes::PARENT
|| sid.1 == classes::SELF
|| sid.1 == classes::STATIC
{
errors.push((
pos,
"Static method calls in expression trees require explicit class names.".into(),
));
return unchanged_result;
}
} else {
errors.push((
pos,
"Expression trees only support function calls and static method calls on named classes.".into(),
));
return unchanged_result;
};
let len = temps.static_method_pointers.len();
temps
.static_method_pointers
.push(static_meth_ptr(&recv.1, &cid, &s));
let temp_variable = temp_static_method_lvar(&recv.1, len);
let desugar_expr = v_meth_call(
et::VISIT_CALL,
vec![
pos_expr.clone(),
v_meth_call(
et::VISIT_STATIC_METHOD,
vec![pos_expr, temp_variable.clone()],
&pos,
),
vec_literal(desugar_args),
],
&pos,
);
let virtual_expr = Expr(
(),
pos.clone(),
Call(Box::new((
static_meth_call(
visitor_name,
et::SYMBOL_TYPE,
vec![temp_variable],
&pos,
),
vec![],
build_args(virtual_args),
None,
))),
);
RewriteResult {
virtual_expr,
desugar_expr,
}
}
// Source: MyDsl`$x->bar()`
// Virtualized: $x->bar()
// Desugared: $0v->visitCall($0v->visitMethodCall(new ExprPos(...), $0v->visitLocal(new ExprPos(...), '$x'), 'bar'), vec[])
ObjGet(og) if og.3 == ast::PropOrMethod::IsMethod => {
let (e1, e2, null_flavor, is_prop_call) = *og;
if null_flavor == OgNullFlavor::OGNullsafe {
errors.push((
pos.clone(),
"Expression Trees do not support nullsafe method calls".into(),
));
}
let rewritten_e1 = rewrite_expr(temps, e1, visitor_name, errors);
let id = if let Id(id) = &e2.2 {
string_literal(id.0.clone(), &id.1)
} else {
errors.push((
pos.clone(),
"Expression trees only support named method calls.".into(),
));
e2.clone()
};
let desugar_expr = v_meth_call(
et::VISIT_CALL,
vec![
pos_expr.clone(),
v_meth_call(
et::VISIT_INSTANCE_METHOD,
vec![pos_expr, rewritten_e1.desugar_expr, id],
&pos,
),
vec_literal(desugar_args),
],
&pos,
);
let virtual_expr = Expr(
(),
pos.clone(),
Call(Box::new((
Expr(
(),
pos,
ObjGet(Box::new((
rewritten_e1.virtual_expr,
e2,
null_flavor,
is_prop_call,
))),
),
vec![],
build_args(virtual_args),
None,
))),
);
RewriteResult {
virtual_expr,
desugar_expr,
}
}
_ => {
let rewritten_recv =
rewrite_expr(temps, Expr((), recv.1, recv.2), visitor_name, errors);
let desugar_expr = v_meth_call(
et::VISIT_CALL,
vec![
pos_expr,
rewritten_recv.desugar_expr,
vec_literal(desugar_args),
],
&pos,
);
let virtual_expr = Expr(
(),
pos,
Call(Box::new((
rewritten_recv.virtual_expr,
vec![],
build_args(virtual_args),
None,
))),
);
RewriteResult {
virtual_expr,
desugar_expr,
}
}
}
}
// Source: MyDsl`($x) ==> { ... }`
// Virtualized: ($x) ==> { ...; return MyDsl::voidType(); }
// if no `return expr;` statements.
// Desugared: $0v->visitLambda(new ExprPos(...), vec['$x'], vec[...]).
Lfun(lf) => {
let mut fun_ = lf.0;
let mut param_names = Vec::with_capacity(fun_.params.len());
for param in &fun_.params {
if param.expr.is_some() {
errors.push((
param.pos.clone(),
"Expression trees do not support parameters with default values.".into(),
));
}
param_names.push(string_literal(param.pos.clone(), ¶m.name));
}
let body = std::mem::take(&mut fun_.body.fb_ast);
let should_append_return = only_void_return(&body);
let (mut virtual_body_stmts, desugar_body) =
rewrite_stmts(temps, body, visitor_name, errors);
if should_append_return {
virtual_body_stmts.push(Stmt(
pos.clone(),
aast::Stmt_::Return(Box::new(Some(static_meth_call(
visitor_name,
et::VOID_TYPE,
vec![],
&pos,
)))),
));
}
let desugar_expr = v_meth_call(
et::VISIT_LAMBDA,
vec![
pos_expr,
vec_literal(param_names),
vec_literal(desugar_body),
],
&pos,
);
fun_.body.fb_ast = virtual_body_stmts;
let virtual_expr = Expr((), pos, Lfun(Box::new((fun_, vec![]))));
RewriteResult {
virtual_expr,
desugar_expr,
}
}
// Source: MyDsl`${ ... }`
// Virtualized to `${ ... }`
// Desugared to `$0v->splice(new ExprPos(...), '$var_name', ...)`
ETSplice(e) => {
if let Err(err) = check_nested_splice(&e) {
errors.push(err);
};
let len = temps.splices.len();
let expr_pos = e.1.clone();
temps.splices.push(*e);
let temp_variable = temp_splice_lvar(&expr_pos, len);
let temp_variable_string = string_literal(expr_pos, &temp_splice_lvar_string(len));
let desugar_expr = v_meth_call(
et::SPLICE,
vec![pos_expr, temp_variable_string, temp_variable.clone()],
&pos,
);
let virtual_expr = Expr((), pos, ETSplice(Box::new(temp_variable)));
RewriteResult {
virtual_expr,
desugar_expr,
}
}
// Source: MyDsl`(...)->foo`
// Virtualized to: `(...)->foo`
// Desugared to `$0v->visitPropertyAccess(new ExprPos(...), ...), 'foo')`
ObjGet(og) => {
let (e1, e2, null_flavor, is_prop_call) = *og;
if null_flavor == OgNullFlavor::OGNullsafe {
errors.push((
pos.clone(),
"Expression Trees do not support nullsafe property access".into(),
));
}
let rewritten_e1 = rewrite_expr(temps, e1, visitor_name, errors);
let id = if let Id(id) = &e2.2 {
string_literal(id.0.clone(), &id.1)
} else {
errors.push((
pos.clone(),
"Expression trees only support named property access.".into(),
));
e2.clone()
};
let desugar_expr = v_meth_call(
et::VISIT_PROPERTY_ACCESS,
vec![pos_expr, rewritten_e1.desugar_expr, id],
&pos,
);
let virtual_expr = Expr(
(),
pos,
ObjGet(Box::new((
rewritten_e1.virtual_expr,
e2,
null_flavor,
is_prop_call,
))),
);
RewriteResult {
virtual_expr,
desugar_expr,
}
}
// Source: MyDsl`<foo my-attr="stuff">text <foo-child/> </foo>`
// Virtualized: <foo my-attr={MyDsl::stringType()}>{MyDsl::stringType()} <foo-child/> </foo>
// Desugared:
// $0v->visitXhp(
// new ExprPos(...),
// :foo::class,
// dict["my-attr" => $0v->visitString(...)],
// vec[
// $0v->visitString(..., "text ")],
// $0v->visitXhp(..., :foo-child::class, ...),
// ],
// )
Xml(xml) => {
let (hint, attrs, children) = *xml;
let mut virtual_attrs = vec![];
let mut desugar_attrs = vec![];
for attr in attrs {
match attr {
aast::XhpAttribute::XhpSimple(xs) => {
let (attr_name_pos, attr_name) = xs.name.clone();
let dict_key =
Expr::new((), attr_name_pos, Expr_::String(BString::from(attr_name)));
let rewritten_attr_expr =
rewrite_expr(temps, xs.expr, visitor_name, errors);
desugar_attrs.push((dict_key, rewritten_attr_expr.desugar_expr));
virtual_attrs.push(aast::XhpAttribute::XhpSimple(aast::XhpSimple {
expr: rewritten_attr_expr.virtual_expr,
..xs
}))
}
aast::XhpAttribute::XhpSpread(e) => {
errors.push((
e.1,
"Expression trees do not support attribute spread syntax.".into(),
));
}
}
}
let (virtual_children, desugar_children) =
rewrite_exprs(temps, children, visitor_name, errors);
// Construct :foo::class.
let hint_pos = hint.0.clone();
let hint_class = Expr_::ClassConst(Box::new((
ClassId(
(),
hint_pos.clone(),
ClassId_::CIexpr(Expr::new(
(),
hint_pos.clone(),
Expr_::Id(Box::new(ast_defs::Id(hint_pos.clone(), hint.1.clone()))),
)),
),
(hint_pos, "class".to_string()),
)));
let virtual_expr = Expr(
(),
pos.clone(),
Xml(Box::new((hint, virtual_attrs, virtual_children))),
);
let desugar_expr = v_meth_call(
et::VISIT_XHP,
vec![
pos_expr,
Expr((), pos.clone(), hint_class),
dict_literal(&pos, desugar_attrs),
vec_literal(desugar_children),
],
&pos,
);
RewriteResult {
virtual_expr,
desugar_expr,
}
}
ClassConst(_) => {
errors.push((
pos,
"Expression trees do not support directly referencing class consts. Consider splicing values defined outside the scope of an Expression Tree using ${...}.".into(),
));
unchanged_result
}
Efun(_) => {
errors.push((
pos,
"Expression trees do not support PHP lambdas. Consider using Hack lambdas `() ==> {}` instead.".into(),
));
unchanged_result
}
ExpressionTree(_) => {
errors.push((
pos,
"Expression trees may not be nested. Consider splicing Expression trees together using `${}`.".into()
));
unchanged_result
}
_ => {
errors.push((pos, "Unsupported expression tree syntax.".into()));
unchanged_result
}
}
}