in hphp/hack/src/parser/rust_parser_errors.rs [2977:3443]
fn expression_errors(&mut self, node: S<'a>) {
if let Some(sl) = self.env.stack_limit.as_ref() {
sl.panic_if_exceeded();
}
let check_is_as_expression = |self_: &mut Self, hint: S<'a>| {
let n = match &node.children {
IsExpression(_) => "is",
_ => "as",
};
match &hint.children {
ClosureTypeSpecifier(_) if self_.env.is_hhvm_compat() => {
self_.errors.push(make_error_from_node(
hint,
errors::invalid_is_as_expression_hint(n, "__Callable"),
));
}
SoftTypeSpecifier(_) => {
self_.errors.push(make_error_from_node(
hint,
errors::invalid_is_as_expression_hint(n, "__Soft"),
));
}
AttributizedSpecifier(x)
if self_.attribute_specification_contains(&x.attribute_spec, "__Soft") =>
{
self_.errors.push(make_error_from_node(
hint,
errors::invalid_is_as_expression_hint(n, "__Soft"),
));
}
_ => {}
}
};
match &node.children {
// We parse the right hand side of `new` as a generic expression, but PHP
// (and therefore Hack) only allow a certain subset of expressions, so we
// should verify here that the expression we parsed is in that subset.
// Refer: https://github.com/php/php-langspec/blob/master/spec/10-expressions.md#instanceof-operator*)
ConstructorCall(ctr_call) => {
for p in syntax_to_list_no_separators(&ctr_call.argument_list) {
self.function_call_argument_errors(true, p);
}
self.class_type_designator_errors(&ctr_call.type_);
if self.env.is_typechecker() {
// attr or list item -> syntax list -> attribute
match self.parents.iter().rev().nth(2) {
Some(a)
if a.is_attribute_specification()
|| a.is_old_attribute_specification()
|| a.is_file_attribute_specification() => {}
_ => {
if ctr_call.left_paren.is_missing() || ctr_call.right_paren.is_missing()
{
let node = &ctr_call.type_;
let constructor_name = self.text(&ctr_call.type_);
self.errors.push(make_error_from_node(
node,
errors::error2038(constructor_name),
));
}
}
}
};
}
LiteralExpression(x) => {
if let Token(token) = &x.expression.children {
if token.kind() == TokenKind::DecimalLiteral
|| token.kind() == TokenKind::DecimalLiteral
{
let text = self.text(&x.expression);
if text.parse::<i64>().is_err() {
let error_text = if token.kind() == TokenKind::DecimalLiteral {
errors::error2071(text)
} else {
errors::error2072(text)
};
self.errors.push(make_error_from_node(node, error_text))
}
}
}
}
SubscriptExpression(x)
if self.env.is_typechecker() && x.left_bracket.is_left_brace() =>
{
self.errors
.push(make_error_from_node(node, errors::error2020))
}
FunctionCallExpression(x) => {
let arg_list = &x.argument_list;
if let Some(h) = misplaced_variadic_arg(arg_list) {
self.errors.push(make_error_from_node(h, errors::error2033))
}
for p in syntax_to_list_no_separators(arg_list) {
self.function_call_argument_errors(false, p)
}
let recv = &x.receiver;
self.function_call_on_xhp_name_errors(recv);
let fun_and_clsmeth_disabled = self
.env
.parser_options
.po_disallow_fun_and_cls_meth_pseudo_funcs;
if strip_ns(self.text(recv)) == strip_ns(sn::readonly::AS_MUT) {
self.mark_uses_readonly()
}
if self.text(recv) == strip_hh_ns(sn::autoimported_functions::FUN_)
&& fun_and_clsmeth_disabled
{
let mut arg_node_list = syntax_to_list_no_separators(arg_list);
match arg_node_list.next() {
Some(name) if arg_node_list.count() == 0 => self.errors.push(
make_error_from_node(recv, errors::fun_disabled(self.text(name))),
),
_ => self.errors.push(make_error_from_node(
recv,
errors::fun_requires_const_string,
)),
}
}
if self.text(recv) == strip_hh_ns(sn::autoimported_functions::CLASS_METH)
&& fun_and_clsmeth_disabled
{
self.errors
.push(make_error_from_node(recv, errors::class_meth_disabled))
}
if self.text(recv) == strip_hh_ns(sn::autoimported_functions::INST_METH)
&& self.env.parser_options.po_disallow_inst_meth
{
self.errors
.push(make_error_from_node(recv, errors::inst_meth_disabled))
}
}
ETSpliceExpression(_) => {
if !self.env.context.active_expression_tree {
self.errors
.push(make_error_from_node(node, errors::splice_outside_et))
}
}
ListExpression(x) if x.members.is_missing() && self.env.is_hhvm_compat() => {
if let Some(Syntax {
children: ForeachStatement(x),
..
}) = self.parents.last()
{
if std::ptr::eq(node, &x.value) {
self.errors.push(make_error_from_node_with_type(
node,
errors::error2077,
ErrorType::RuntimeError,
))
}
}
}
ListExpression(_) => {
if self
.parents
.last()
.map_or(false, |e| e.is_return_statement())
{
self.errors
.push(make_error_from_node(node, errors::list_must_be_lvar))
}
}
ShapeExpression(x) => {
for f in syntax_to_list_no_separators(&x.fields).rev() {
self.invalid_shape_field_check(f)
}
}
DecoratedExpression(x) => {
let decorator = &x.decorator;
if token_kind(decorator) == Some(TokenKind::Await) {
self.await_as_an_expression_errors(node)
}
}
YieldExpression(_) => {
if self.is_in_unyieldable_magic_method() {
self.errors
.push(make_error_from_node(node, errors::yield_in_magic_methods))
}
if self.env.context.active_callable.is_none() {
self.errors
.push(make_error_from_node(node, errors::yield_outside_function))
}
if self.has_inout_params() {
let e = if self.is_inside_async_method() {
errors::inout_param_in_async_generator
} else {
errors::inout_param_in_generator
};
self.errors.push(make_error_from_node_with_type(
node,
e,
ErrorType::RuntimeError,
))
}
}
ScopeResolutionExpression(x) => {
let qualifier = &x.qualifier;
let name = &x.name;
let (is_dynamic_name, is_self_or_parent, is_valid) =
// PHP langspec allows string literals, variables
// qualified names, static, self and parent as valid qualifiers
// We do not allow string literals in hack
match (&qualifier.children, token_kind(qualifier)) {
(LiteralExpression(_), _) => (false, false, false),
(QualifiedName(_), _) => (false, false, true),
(_, Some(TokenKind::Name))
| (_, Some(TokenKind::XHPClassName))
| (_, Some(TokenKind::Static)) => (false, false, true),
(_, Some(TokenKind::SelfToken)) | (_, Some(TokenKind::Parent)) => {
(false, true, true)
}
// ${}::class
(PrefixUnaryExpression(x), _)
if token_kind(&x.operator) == Some(TokenKind::Dollar) =>
{
(true, false, true)
}
(PipeVariableExpression(_), _)
| (VariableExpression(_), _)
| (SimpleTypeSpecifier(_), _)
| (GenericTypeSpecifier(_), _) => (true, false, true),
_ => (true, false, false),
};
if !is_valid {
self.errors.push(make_error_from_node(
node,
errors::invalid_scope_resolution_qualifier,
))
}
let is_name_class = self.text(name).eq_ignore_ascii_case("class");
if (is_dynamic_name || !is_valid) && is_name_class {
self.errors.push(make_error_from_node(
node,
errors::coloncolonclass_on_dynamic,
))
}
let text_name = self.text(qualifier);
let is_name_namespace = text_name.eq_ignore_ascii_case("namespace");
if is_name_namespace {
self.errors.push(make_error_from_node(
node,
errors::namespace_not_a_classname,
))
}
if is_self_or_parent && is_name_class && !self.is_in_active_class_scope() {
self.errors.push(make_error_from_node_with_type(
node,
errors::self_or_parent_colon_colon_class_outside_of_class(text_name),
ErrorType::RuntimeError,
))
}
}
PrefixUnaryExpression(x) if token_kind(&x.operator) == Some(TokenKind::Dollar) => {
if check_prefix_unary_dollar(node) {
self.errors
.push(make_error_from_node(node, errors::dollar_unary))
}
}
// TODO(T21285960): Remove this bug-port, stemming from T22184312
LambdaExpression(x)
if self.env.is_hhvm_compat()
&& !x.async_.is_missing()
&& x.async_.trailing_width() == 0
&& x.signature.leading_width() == 0 =>
{
self.errors
.push(make_error_from_node(node, errors::error1057("==>")))
}
// End of bug-port
IsExpression(x) => check_is_as_expression(self, &x.right_operand),
AsExpression(x) => check_is_as_expression(self, &x.right_operand),
ConditionalExpression(x) => {
if x.consequence.is_missing() && self.env.is_typechecker() {
self.errors
.push(make_error_from_node(node, errors::elvis_operator_space))
}
if x.test.is_conditional_expression() && self.env.is_typechecker() {
self.errors
.push(make_error_from_node(node, errors::nested_ternary))
}
match &x.alternative.children {
LambdaExpression(x)
if x.body.is_conditional_expression() && self.env.is_typechecker() =>
{
self.errors
.push(make_error_from_node(node, errors::nested_ternary))
}
_ => {}
}
}
LambdaExpression(x) => {
self.no_memoize_attribute_on_lambda(&x.attribute_spec);
self.no_async_before_lambda_body(&x.body);
}
AnonymousFunction(x) => self.no_memoize_attribute_on_lambda(&x.attribute_spec),
AwaitableCreationExpression(x) => {
self.no_memoize_attribute_on_lambda(&x.attribute_spec)
}
CollectionLiteralExpression(x) => {
enum Status {
ValidClass(String),
InvalidClass,
InvalidBraceKind,
}
use Status::*;
let n = &x.name;
let initializers = &x.initializers;
let is_standard_collection = |lc_name: &str| {
lc_name.eq_ignore_ascii_case("pair")
|| lc_name.eq_ignore_ascii_case("vector")
|| lc_name.eq_ignore_ascii_case("map")
|| lc_name.eq_ignore_ascii_case("set")
|| lc_name.eq_ignore_ascii_case("immvector")
|| lc_name.eq_ignore_ascii_case("immmap")
|| lc_name.eq_ignore_ascii_case("immset")
};
let use_key_value_initializers = |lc_name: &str| {
lc_name.eq_ignore_ascii_case("map") || lc_name.eq_ignore_ascii_case("immmap")
};
let is_qualified_std_collection = |l, r| {
token_kind(l) == Some(TokenKind::Name)
&& token_kind(r) == Some(TokenKind::Name)
&& self.text(l).eq_ignore_ascii_case("hh")
&& is_standard_collection(self.text(r))
};
let check_type_specifier = |n, t: &PositionedToken<'a>| {
if t.kind() == TokenKind::Name {
match self.text(n).to_ascii_lowercase().as_ref() {
"dict" | "vec" | "keyset" => InvalidBraceKind,
n => {
if is_standard_collection(n) {
ValidClass(n.to_string())
} else {
InvalidClass
}
}
}
} else {
InvalidClass
}
};
let check_qualified_name = |parts| {
let mut parts = syntax_to_list(false, parts);
let p1 = parts.next();
let p2 = parts.next();
let p3 = parts.next();
let p4 = parts.next();
match (p1, p2, p3, p4) {
(Some(l), Some(r), None, None)
if self.namespace_name == GLOBAL_NAMESPACE_NAME
&& is_qualified_std_collection(l, r) =>
{
// HH\Vector in global namespace
ValidClass(self.text(r).to_ascii_lowercase())
}
(Some(missing), Some(l), Some(r), None)
if missing.is_missing() && is_qualified_std_collection(l, r) =>
{
// \HH\Vector
ValidClass(self.text(r).to_ascii_lowercase())
}
_ => InvalidClass,
}
};
let status = match &n.children {
// non-qualified name
SimpleTypeSpecifier(x) => match &x.specifier.children {
Token(t) => check_type_specifier(&x.specifier, t),
QualifiedName(x) => check_qualified_name(&x.parts),
_ => InvalidClass,
},
GenericTypeSpecifier(x) => match &x.class_type.children {
Token(t) => check_type_specifier(&x.class_type, t),
QualifiedName(x) => check_qualified_name(&x.parts),
_ => InvalidClass,
},
_ => InvalidClass,
};
let is_key_value = |s: S<'a>| {
if let ElementInitializer(_) = s.children {
true
} else {
false
}
};
let initializer_list = || syntax_to_list_no_separators(initializers);
let num_initializers = initializer_list().count();
match &status {
ValidClass(name)
if use_key_value_initializers(name)
&& initializer_list().any(|i| !is_key_value(i)) =>
{
self.errors.push(make_error_from_node(
node,
errors::invalid_value_initializer(self.text(n)),
));
}
ValidClass(name)
if !use_key_value_initializers(name)
&& initializer_list().any(|i| is_key_value(i)) =>
{
self.errors.push(make_error_from_node(
node,
errors::invalid_key_value_initializer(self.text(n)),
));
}
ValidClass(pair) if pair == "pair" && num_initializers != 2 => {
let msg = if num_initializers == 0 {
errors::pair_initializer_needed
} else {
errors::pair_initializer_arity
};
self.errors.push(make_error_from_node_with_type(
node,
msg,
ErrorType::RuntimeError,
));
}
ValidClass(_) => {}
InvalidBraceKind => self.errors.push(make_error_from_node(
node,
errors::invalid_brace_kind_in_collection_initializer,
)),
InvalidClass => self.errors.push(make_error_from_node(
node,
errors::invalid_class_in_collection_initializer,
)),
}
}
PrefixUnaryExpression(x) if token_kind(&x.operator) == Some(TokenKind::Await) => {
self.await_as_an_expression_errors(node)
}
PrefixUnaryExpression(x) if token_kind(&x.operator) == Some(TokenKind::Readonly) => {
self.mark_uses_readonly()
}
// Other kinds of expressions currently produce no expr errors.
_ => {}
}
}