in hphp/hack/src/parser/lowerer/lowerer.rs [4408:4913]
fn p_class_elt_<'a>(class: &mut ast::Class_, node: S<'a>, env: &mut Env<'a>) -> Result<()> {
use ast::Visibility;
let doc_comment_opt = extract_docblock(node, env);
let has_fun_header =
|m: &MethodishDeclarationChildren<'_, PositionedToken<'a>, PositionedValue<'a>>| {
matches!(
m.function_decl_header.children,
FunctionDeclarationHeader(_)
)
};
let p_method_vis = |node: S<'a>, name_pos: &Pos, env: &mut Env<'a>| -> Result<Visibility> {
match p_visibility_last_win(node, env)? {
None => {
raise_hh_error(env, Naming::method_needs_visibility(name_pos.clone()));
Ok(Visibility::Public)
}
Some(v) => Ok(v),
}
};
match &node.children {
ConstDeclaration(c) => {
let user_attributes = p_user_attributes(&c.attribute_spec, env)?;
let kinds = p_kinds(&c.modifiers, env)?;
let has_abstract = kinds.has(modifier::ABSTRACT);
// TODO: make wrap `type_` `doc_comment` by `Rc` in ClassConst to avoid clone
let type_ = map_optional(&c.type_specifier, env, p_hint)?;
// ocaml's behavior is that if anything throw, it will
// discard all lowered elements. So adding to class
// must be at the last.
let mut class_consts = could_map(&c.declarators, env, |n, e| match &n.children {
ConstantDeclarator(c) => {
let id = pos_name(&c.name, e)?;
use aast::ClassConstKind::*;
let kind = if has_abstract {
CCAbstract(map_optional(&c.initializer, e, p_simple_initializer)?)
} else {
CCConcrete(p_simple_initializer(&c.initializer, e)?)
};
Ok(ast::ClassConst {
user_attributes: user_attributes.clone(),
type_: type_.clone(),
id,
kind,
doc_comment: doc_comment_opt.clone(),
})
}
_ => missing_syntax("constant declarator", n, e),
})?;
Ok(class.consts.append(&mut class_consts))
}
TypeConstDeclaration(c) => {
use ast::ClassTypeconst::{TCAbstract, TCConcrete};
if !c.type_parameters.is_missing() {
raise_parsing_error(node, env, &syntax_error::tparams_in_tconst);
}
let user_attributes = p_user_attributes(&c.attribute_spec, env)?;
let type__ = map_optional(&c.type_specifier, env, p_hint)?
.map(|hint| soften_hint(&user_attributes, hint));
let kinds = p_kinds(&c.modifiers, env)?;
let name = pos_name(&c.name, env)?;
let as_constraint = map_optional(&c.type_constraint, env, p_tconstraint_ty)?;
let span = p_pos(node, env);
let has_abstract = kinds.has(modifier::ABSTRACT);
let kind = if has_abstract {
TCAbstract(ast::ClassAbstractTypeconst {
as_constraint,
super_constraint: None,
default: type__,
})
} else if let Some(type_) = type__ {
if env.is_typechecker() && as_constraint.is_some() {
raise_hh_error(
env,
NastCheck::partially_abstract_typeconst_definition(name.0.clone()),
);
}
TCConcrete(ast::ClassConcreteTypeconst { c_tc_type: type_ })
} else {
raise_hh_error(
env,
NastCheck::not_abstract_without_typeconst(name.0.clone()),
);
missing_syntax("value for the type constant", node, env)?
};
Ok(class.typeconsts.push(ast::ClassTypeconstDef {
name,
kind,
user_attributes,
span,
doc_comment: doc_comment_opt,
is_ctx: false,
}))
}
ContextConstDeclaration(c) => {
use ast::ClassTypeconst::{TCAbstract, TCConcrete};
if !c.type_parameters.is_missing() {
raise_parsing_error(node, env, &syntax_error::tparams_in_tconst);
}
let name = pos_name(&c.name, env)?;
let context = p_context_list_to_intersection(
&c.ctx_list,
env,
"Context constants cannot alias polymorphic contexts",
)?;
if let Some(ref hint) = context {
use ast::Hint_::{Happly, Hintersection};
let ast::Hint(_, ref h) = hint;
if let Hintersection(hl) = &**h {
for h in hl {
let ast::Hint(_, ref h) = h;
if let Happly(oxidized::ast::Id(_, id), _) = &**h {
if id.as_str().ends_with("_local") {
raise_parsing_error(
&c.ctx_list,
env,
"Local contexts on ctx constants are not allowed",
);
}
}
}
}
}
let span = p_pos(node, env);
let kinds = p_kinds(&c.modifiers, env)?;
let has_abstract = kinds.has(modifier::ABSTRACT);
let (super_constraint, as_constraint) = p_ctx_constraints(&c.constraint, env)?;
let kind = if has_abstract {
TCAbstract(ast::ClassAbstractTypeconst {
as_constraint,
super_constraint,
default: context,
})
} else if let Some(c_tc_type) = context {
if env.is_typechecker() && (super_constraint.is_some() || as_constraint.is_some()) {
raise_parsing_error(
node,
env,
"Constraints on a context constant requires it to be abstract",
)
};
TCConcrete(ast::ClassConcreteTypeconst { c_tc_type })
} else {
raise_hh_error(
env,
NastCheck::not_abstract_without_typeconst(name.0.clone()),
);
missing_syntax("value for the context constant", node, env)?
};
Ok(class.typeconsts.push(ast::ClassTypeconstDef {
name,
kind,
user_attributes: vec![],
span,
doc_comment: doc_comment_opt,
is_ctx: true,
}))
}
PropertyDeclaration(c) => {
let user_attributes = p_user_attributes(&c.attribute_spec, env)?;
let type_ =
map_optional(&c.type_, env, p_hint)?.map(|t| soften_hint(&user_attributes, t));
let kinds = p_kinds(&c.modifiers, env)?;
let vis = p_visibility_last_win_or(&c.modifiers, env, Visibility::Public)?;
let doc_comment = if env.quick_mode {
None
} else {
doc_comment_opt
};
let name_exprs = could_map(&c.declarators, env, |n, e| match &n.children {
PropertyDeclarator(c) => {
let name = pos_name_(&c.name, e, Some('$'))?;
let pos = p_pos(n, e);
let expr = map_optional(&c.initializer, e, p_simple_initializer)?;
Ok((pos, name, expr))
}
_ => missing_syntax("property declarator", n, e),
})?;
for (i, name_expr) in name_exprs.into_iter().enumerate() {
class.vars.push(ast::ClassVar {
final_: kinds.has(modifier::FINAL),
xhp_attr: None,
abstract_: kinds.has(modifier::ABSTRACT),
readonly: kinds.has(modifier::READONLY),
visibility: vis,
type_: ast::TypeHint((), type_.clone()),
id: name_expr.1,
expr: name_expr.2,
user_attributes: user_attributes.clone(),
doc_comment: if i == 0 { doc_comment.clone() } else { None },
is_promoted_variadic: false,
is_static: kinds.has(modifier::STATIC),
span: name_expr.0,
});
}
Ok(())
}
MethodishDeclaration(c) if has_fun_header(c) => {
// keep cls_generics
*env.fn_generics_mut() = HashMap::default();
let classvar_init = |param: &ast::FunParam| -> (ast::Stmt, ast::ClassVar) {
let cvname = drop_prefix(¶m.name, '$');
let p = ¶m.pos;
let span = match ¶m.expr {
Some(ast::Expr(_, pos_end, _)) => {
Pos::btw(p, pos_end).unwrap_or_else(|_| p.clone())
}
_ => p.clone(),
};
let e = |expr_: Expr_| -> ast::Expr { ast::Expr::new((), p.clone(), expr_) };
let lid = |s: &str| -> ast::Lid { ast::Lid(p.clone(), (0, s.to_string())) };
(
ast::Stmt::new(
p.clone(),
ast::Stmt_::mk_expr(e(Expr_::mk_binop(
ast::Bop::Eq(None),
e(Expr_::mk_obj_get(
e(Expr_::mk_lvar(lid(special_idents::THIS))),
e(Expr_::mk_id(ast::Id(p.clone(), cvname.to_string()))),
ast::OgNullFlavor::OGNullthrows,
ast::PropOrMethod::IsProp,
)),
e(Expr_::mk_lvar(lid(¶m.name))),
))),
),
ast::ClassVar {
final_: false,
xhp_attr: None,
abstract_: false,
// We use the param readonlyness here to represent the
// ClassVar's readonlyness once lowered
// TODO(jjwu): Convert this to an enum when we support
// multiple types of readonlyness
readonly: param.readonly.is_some(),
visibility: param.visibility.unwrap(),
type_: param.type_hint.clone(),
id: ast::Id(p.clone(), cvname.to_string()),
expr: None,
user_attributes: param.user_attributes.clone(),
doc_comment: None,
is_promoted_variadic: param.is_variadic,
is_static: false,
span,
},
)
};
let header = &c.function_decl_header;
let h = match &header.children {
FunctionDeclarationHeader(h) => h,
_ => panic!(),
};
let hdr = p_fun_hdr(header, env)?;
let (mut member_init, mut member_def): (Vec<ast::Stmt>, Vec<ast::ClassVar>) = hdr
.parameters
.iter()
.filter_map(|p| p.visibility.map(|_| classvar_init(p)))
.unzip();
let kinds = p_kinds(&h.modifiers, env)?;
let visibility = p_method_vis(&h.modifiers, &hdr.name.0, env)?;
let is_static = kinds.has(modifier::STATIC);
let readonly_this = kinds.has(modifier::READONLY);
*env.in_static_method() = is_static;
check_effect_polymorphic_reification(hdr.contexts.as_ref(), env, node);
let (mut body, body_has_yield) = map_yielding(&c.function_body, env, p_function_body)?;
if env.codegen() {
member_init.reverse();
}
member_init.append(&mut body);
let body = member_init;
*env.in_static_method() = false;
let is_abstract = kinds.has(modifier::ABSTRACT);
let is_external = !is_abstract && c.function_body.is_external();
let user_attributes = p_user_attributes(&c.attribute, env)?;
check_effect_memoized(hdr.contexts.as_ref(), &user_attributes, "method", env);
let method = ast::Method_ {
span: p_fun_pos(node, env),
annotation: (),
final_: kinds.has(modifier::FINAL),
readonly_this,
abstract_: is_abstract,
static_: is_static,
name: hdr.name,
visibility,
tparams: hdr.type_parameters,
where_constraints: hdr.constrs,
params: hdr.parameters,
ctxs: hdr.contexts,
unsafe_ctxs: hdr.unsafe_contexts,
body: ast::FuncBody { fb_ast: body },
fun_kind: mk_fun_kind(hdr.suspension_kind, body_has_yield),
user_attributes,
readonly_ret: hdr.readonly_return,
ret: ast::TypeHint((), hdr.return_type),
external: is_external,
doc_comment: doc_comment_opt,
};
class.vars.append(&mut member_def);
Ok(class.methods.push(method))
}
TraitUseConflictResolution(c) => {
type Ret = Result<Either<ast::InsteadofAlias, ast::UseAsAlias>>;
let p_item = |n: S<'a>, e: &mut Env<'a>| -> Ret {
match &n.children {
TraitUsePrecedenceItem(c) => {
let (qualifier, name) = match &c.name.children {
ScopeResolutionExpression(c) => {
(pos_name(&c.qualifier, e)?, p_pstring(&c.name, e)?)
}
_ => missing_syntax("trait use precedence item", n, e)?,
};
let removed_names = could_map(&c.removed_names, e, pos_name)?;
raise_hh_error(e, Naming::unsupported_instead_of(name.0.clone()));
Ok(Either::Left(ast::InsteadofAlias(
qualifier,
name,
removed_names,
)))
}
TraitUseAliasItem(c) => {
let (qualifier, name) = match &c.aliasing_name.children {
ScopeResolutionExpression(c) => {
(Some(pos_name(&c.qualifier, e)?), p_pstring(&c.name, e)?)
}
_ => (None, p_pstring(&c.aliasing_name, e)?),
};
let (kinds, mut vis_raw) = p_modifiers(
|mut acc, kind| -> Vec<ast::UseAsVisibility> {
if let Some(v) = modifier::to_use_as_visibility(kind) {
acc.push(v);
}
acc
},
vec![],
&c.modifiers,
e,
)?;
let vis = if kinds.is_empty() || kinds.has_any(modifier::VISIBILITIES) {
vis_raw
} else {
let mut v = vec![ast::UseAsVisibility::UseAsPublic];
v.append(&mut vis_raw);
v
};
let aliased_name = if !c.aliased_name.is_missing() {
Some(pos_name(&c.aliased_name, e)?)
} else {
None
};
raise_hh_error(e, Naming::unsupported_trait_use_as(name.0.clone()));
Ok(Either::Right(ast::UseAsAlias(
qualifier,
name,
aliased_name,
vis,
)))
}
_ => missing_syntax("trait use conflict resolution item", n, e),
}
};
let mut uses = could_map(&c.names, env, p_hint)?;
let elts = could_map(&c.clauses, env, p_item)?;
class.uses.append(&mut uses);
for elt in elts.into_iter() {
match elt {
Either::Left(l) => class.insteadof_alias.push(l),
Either::Right(r) => class.use_as_alias.push(r),
}
}
Ok(())
}
TraitUse(c) => {
let mut uses = could_map(&c.names, env, p_hint)?;
Ok(class.uses.append(&mut uses))
}
RequireClause(c) => {
use aast::RequireKind::*;
let hint = p_hint(&c.name, env)?;
let require_kind = match token_kind(&c.kind) {
Some(TK::Implements) => RequireImplements,
Some(TK::Extends) => RequireExtends,
Some(TK::Class) => RequireClass,
_ => missing_syntax("trait require kind", &c.kind, env)?,
};
Ok(class.reqs.push((hint, require_kind)))
}
XHPClassAttributeDeclaration(c) => {
type Ret = Result<Either<ast::XhpAttr, ast::Hint>>;
let p_attr = |node: S<'a>, env: &mut Env<'a>| -> Ret {
let mk_attr_use = |n: S<'a>, env: &mut Env<'a>| {
Ok(Either::Right(ast::Hint(
p_pos(n, env),
Box::new(ast::Hint_::Happly(pos_name(n, env)?, vec![])),
)))
};
match &node.children {
XHPClassAttribute(c) => {
let ast::Id(p, name) = pos_name(&c.name, env)?;
if let TypeConstant(_) = &c.type_.children {
if env.is_typechecker() {
raise_parsing_error(
&c.type_,
env,
&syntax_error::xhp_class_attribute_type_constant,
)
}
}
let req = match &c.required.children {
XHPRequired(_) => Some(ast::XhpAttrTag::Required),
XHPLateinit(_) => Some(ast::XhpAttrTag::LateInit),
_ => None,
};
let pos = if c.initializer.is_missing() {
p.clone()
} else {
Pos::btw(&p, &p_pos(&c.initializer, env)).map_err(Error::Failwith)?
};
let (hint, like, enum_values, enum_) = match &c.type_.children {
XHPEnumType(c1) => {
let p = p_pos(&c.type_, env);
let like = match &c1.like.children {
Missing => None,
_ => Some(p_pos(&c1.like, env)),
};
let vals = could_map(&c1.values, env, p_expr)?;
let mut enum_vals = vec![];
for val in vals.clone() {
match val {
ast::Expr(_, _, Expr_::String(xev)) => enum_vals
.push(ast::XhpEnumValue::XEVString(xev.to_string())),
ast::Expr(_, _, Expr_::Int(xev)) => match xev.parse() {
Ok(n) => enum_vals.push(ast::XhpEnumValue::XEVInt(n)),
Err(_) =>
// Since we have parse checks for
// malformed integer literals already,
// we assume this won't happen and ignore
// the case.
{}
},
_ => {}
}
}
(None, like, enum_vals, Some((p, vals)))
}
_ => (Some(p_hint(&c.type_, env)?), None, vec![], None),
};
let init_expr = map_optional(&c.initializer, env, p_simple_initializer)?;
let xhp_attr = ast::XhpAttr(
ast::TypeHint((), hint.clone()),
ast::ClassVar {
final_: false,
xhp_attr: Some(ast::XhpAttrInfo {
like,
tag: req,
enum_values,
}),
abstract_: false,
readonly: false,
visibility: ast::Visibility::Public,
type_: ast::TypeHint((), hint),
id: ast::Id(p, String::from(":") + &name),
expr: init_expr,
user_attributes: vec![],
doc_comment: None,
is_promoted_variadic: false,
is_static: false,
span: pos,
},
req,
enum_,
);
Ok(Either::Left(xhp_attr))
}
XHPSimpleClassAttribute(c) => mk_attr_use(&c.type_, env),
Token(_) => mk_attr_use(node, env),
_ => missing_syntax("XHP attribute", node, env),
}
};
let attrs = could_map(&c.attributes, env, p_attr)?;
for attr in attrs.into_iter() {
match attr {
Either::Left(attr) => class.xhp_attrs.push(attr),
Either::Right(xhp_attr_use) => class.xhp_attr_uses.push(xhp_attr_use),
}
}
Ok(())
}
XHPChildrenDeclaration(c) => {
let p = p_pos(node, env);
Ok(class
.xhp_children
.push((p, p_xhp_child(&c.expression, env)?)))
}
XHPCategoryDeclaration(c) => {
let p = p_pos(node, env);
let categories = could_map(&c.categories, env, |n, e| p_pstring_(n, e, Some('%')))?;
if let Some((_, cs)) = &class.xhp_category {
if let Some(category) = cs.first() {
raise_hh_error(env, NastCheck::multiple_xhp_category(category.0.clone()))
}
}
Ok(class.xhp_category = Some((p, categories)))
}
_ => missing_syntax("class element", node, env),
}
}