in hphp/hack/src/parser/lowerer/lowerer.rs [5119:5611]
fn p_def<'a>(node: S<'a>, env: &mut Env<'a>) -> Result<Vec<ast::Def>> {
let doc_comment_opt = extract_docblock(node, env);
match &node.children {
FunctionDeclaration(FunctionDeclarationChildren {
attribute_spec,
declaration_header,
body,
}) => {
let mut env = Env::clone_and_unset_toplevel_if_toplevel(env);
let env = env.as_mut();
env.clear_generics();
let hdr = p_fun_hdr(declaration_header, env)?;
let is_external = body.is_external();
let (block, yield_) = if is_external {
(vec![], false)
} else {
map_yielding(body, env, p_function_body)?
};
let user_attributes = p_user_attributes(attribute_spec, env)?;
check_effect_memoized(hdr.contexts.as_ref(), &user_attributes, "function", env);
check_context_has_this(hdr.contexts.as_ref(), env);
let ret = ast::TypeHint((), hdr.return_type);
let fun = ast::Fun_ {
span: p_fun_pos(node, env),
readonly_this: hdr.readonly_this,
annotation: (),
ret,
readonly_ret: hdr.readonly_return,
name: hdr.name,
tparams: hdr.type_parameters,
where_constraints: hdr.constrs,
params: hdr.parameters,
ctxs: hdr.contexts,
unsafe_ctxs: hdr.unsafe_contexts,
body: ast::FuncBody { fb_ast: block },
fun_kind: mk_fun_kind(hdr.suspension_kind, yield_),
user_attributes,
external: is_external,
doc_comment: doc_comment_opt,
};
Ok(vec![ast::Def::mk_fun(ast::FunDef {
namespace: mk_empty_ns_env(env),
file_attributes: vec![],
mode: env.file_mode(),
fun,
})])
}
ClassishDeclaration(c) if contains_class_body(c) => {
let mut env = Env::clone_and_unset_toplevel_if_toplevel(env);
let env = env.as_mut();
let mode = env.file_mode();
let user_attributes = p_user_attributes(&c.attribute, env)?;
let kinds = p_kinds(&c.modifiers, env)?;
let final_ = kinds.has(modifier::FINAL);
let is_xhp = matches!(
token_kind(&c.name),
Some(TK::XHPElementName) | Some(TK::XHPClassName)
);
let has_xhp_keyword = matches!(token_kind(&c.xhp), Some(TK::XHP));
let name = pos_name(&c.name, env)?;
env.clear_generics();
let tparams = p_tparam_l(true, &c.type_parameters, env)?;
let class_kind = match token_kind(&c.keyword) {
Some(TK::Class) if kinds.has(modifier::ABSTRACT) => {
ast::ClassishKind::Cclass(ast::Abstraction::Abstract)
}
Some(TK::Class) => ast::ClassishKind::Cclass(ast::Abstraction::Concrete),
Some(TK::Interface) => ast::ClassishKind::Cinterface,
Some(TK::Trait) => ast::ClassishKind::Ctrait,
Some(TK::Enum) => ast::ClassishKind::Cenum,
_ => missing_syntax("class kind", &c.keyword, env)?,
};
let extends = could_map(&c.extends_list, env, p_hint)?;
*env.parent_maybe_reified() = match extends.first().map(|h| h.1.as_ref()) {
Some(ast::Hint_::Happly(_, hl)) => !hl.is_empty(),
_ => false,
};
let implements = could_map(&c.implements_list, env, p_hint)?;
let where_constraints = p_where_constraint(true, node, &c.where_clause, env)?;
let namespace = mk_empty_ns_env(env);
let span = p_pos(node, env);
let mut class_ = ast::Class_ {
span,
annotation: (),
mode,
final_,
is_xhp,
has_xhp_keyword,
kind: class_kind,
name,
tparams,
extends,
uses: vec![],
use_as_alias: vec![],
insteadof_alias: vec![],
xhp_attr_uses: vec![],
xhp_category: None,
reqs: vec![],
implements,
where_constraints,
consts: vec![],
typeconsts: vec![],
vars: vec![],
methods: vec![],
// TODO: what is this attbiute? check ast_to_aast
attributes: vec![],
xhp_children: vec![],
xhp_attrs: vec![],
namespace,
user_attributes,
file_attributes: vec![],
enum_: None,
doc_comment: doc_comment_opt,
emit_id: None,
};
match &c.body.children {
ClassishBody(c1) => {
for elt in c1.elements.syntax_node_to_list_skip_separator() {
p_class_elt(&mut class_, elt, env)?;
}
}
_ => missing_syntax("classish body", &c.body, env)?,
}
Ok(vec![ast::Def::mk_class(class_)])
}
ConstDeclaration(c) => {
let ty = &c.type_specifier;
let decls = c.declarators.syntax_node_to_list_skip_separator();
let mut defs = vec![];
for decl in decls {
let def = match &decl.children {
ConstantDeclarator(c) => {
let name = &c.name;
let init = &c.initializer;
let gconst = ast::Gconst {
annotation: (),
mode: env.file_mode(),
name: pos_name(name, env)?,
type_: map_optional(ty, env, p_hint)?,
value: p_simple_initializer(init, env)?,
namespace: mk_empty_ns_env(env),
span: p_pos(node, env),
emit_id: None,
};
ast::Def::mk_constant(gconst)
}
_ => missing_syntax("constant declaration", decl, env)?,
};
defs.push(def);
}
Ok(defs)
}
AliasDeclaration(c) => {
let tparams = p_tparam_l(false, &c.generic_parameter, env)?;
for tparam in tparams.iter() {
if tparam.reified != ast::ReifyKind::Erased {
raise_parsing_error(node, env, &syntax_error::invalid_reified)
}
}
Ok(vec![ast::Def::mk_typedef(ast::Typedef {
annotation: (),
name: pos_name(&c.name, env)?,
tparams,
constraint: map_optional(&c.constraint, env, p_tconstraint)?.map(|x| x.1),
user_attributes: itertools::concat(
c.attribute_spec
.syntax_node_to_list_skip_separator()
.map(|attr| p_user_attribute(attr, env))
.collect::<Result<Vec<Vec<_>>, _>>()?,
),
file_attributes: vec![],
namespace: mk_empty_ns_env(env),
mode: env.file_mode(),
vis: match token_kind(&c.keyword) {
Some(TK::Type) => ast::TypedefVisibility::Transparent,
Some(TK::Newtype) => ast::TypedefVisibility::Opaque,
_ => missing_syntax("kind", &c.keyword, env)?,
},
kind: p_hint(&c.type_, env)?,
span: p_pos(node, env),
emit_id: None,
is_ctx: false,
})])
}
ContextAliasDeclaration(c) => {
let (_super_constraint, as_constraint) = p_ctx_constraints(&c.as_constraint, env)?;
let pos_name = pos_name(&c.name, env)?;
if let Some(first_char) = pos_name.1.chars().next() {
if first_char.is_lowercase() {
raise_parsing_error(
&c.name,
env,
&syntax_error::user_ctx_should_be_caps(&pos_name.1),
)
}
}
if as_constraint.is_none() {
raise_parsing_error(
&c.name,
env,
&syntax_error::user_ctx_require_as(&pos_name.1),
)
}
let kind = match p_context_list_to_intersection(
&c.context,
env,
"Context aliases cannot alias polymorphic contexts",
)? {
Some(h) => h,
None => {
let pos = pos_name.0.clone();
let hint_ =
ast::Hint_::Happly(ast::Id(pos.clone(), String::from("defaults")), vec![]);
ast::Hint::new(pos, hint_)
}
};
Ok(vec![ast::Def::mk_typedef(ast::Typedef {
annotation: (),
name: pos_name,
tparams: vec![],
constraint: as_constraint,
user_attributes: itertools::concat(
c.attribute_spec
.syntax_node_to_list_skip_separator()
.map(|attr| p_user_attribute(attr, env))
.collect::<Result<Vec<Vec<_>>, _>>()?,
),
namespace: mk_empty_ns_env(env),
mode: env.file_mode(),
file_attributes: vec![],
vis: ast::TypedefVisibility::Opaque,
kind,
span: p_pos(node, env),
emit_id: None,
is_ctx: true,
})])
}
EnumDeclaration(c) => {
let p_enumerator = |n: S<'a>, e: &mut Env<'a>| -> Result<ast::ClassConst> {
match &n.children {
Enumerator(c) => Ok(ast::ClassConst {
user_attributes: vec![],
type_: None,
id: pos_name(&c.name, e)?,
kind: ast::ClassConstKind::CCConcrete(p_expr(&c.value, e)?),
doc_comment: None,
}),
_ => missing_syntax("enumerator", n, e),
}
};
let mut includes = vec![];
let mut p_enum_use = |n: S<'a>, e: &mut Env<'a>| -> Result<()> {
match &n.children {
EnumUse(c) => {
let mut uses = could_map(&c.names, e, p_hint)?;
Ok(includes.append(&mut uses))
}
_ => missing_syntax("enum_use", node, e),
}
};
for elt in c.use_clauses.syntax_node_to_list_skip_separator() {
p_enum_use(elt, env)?;
}
Ok(vec![ast::Def::mk_class(ast::Class_ {
annotation: (),
mode: env.file_mode(),
user_attributes: p_user_attributes(&c.attribute_spec, env)?,
file_attributes: vec![],
final_: false,
kind: ast::ClassishKind::Cenum,
is_xhp: false,
has_xhp_keyword: false,
name: pos_name(&c.name, env)?,
tparams: vec![],
extends: vec![],
implements: vec![],
where_constraints: vec![],
consts: could_map(&c.enumerators, env, p_enumerator)?,
namespace: mk_empty_ns_env(env),
span: p_pos(node, env),
enum_: Some(ast::Enum_ {
base: p_hint(&c.base, env)?,
constraint: map_optional(&c.type_, env, p_tconstraint_ty)?,
includes,
}),
doc_comment: doc_comment_opt,
uses: vec![],
use_as_alias: vec![],
insteadof_alias: vec![],
xhp_attr_uses: vec![],
xhp_category: None,
reqs: vec![],
vars: vec![],
typeconsts: vec![],
methods: vec![],
attributes: vec![],
xhp_children: vec![],
xhp_attrs: vec![],
emit_id: None,
})])
}
EnumClassDeclaration(c) => {
let name = pos_name(&c.name, env)?;
// Adding __EnumClass
let mut user_attributes = p_user_attributes(&c.attribute_spec, env)?;
let enum_class_attribute = ast::UserAttribute {
name: ast::Id(name.0.clone(), special_attrs::ENUM_CLASS.to_string()),
params: vec![],
};
user_attributes.push(enum_class_attribute);
// During lowering we store the base type as is. It will be updated during
// the naming phase
let base_type = p_hint(&c.base, env)?;
let name_s = name.1.clone(); // TODO: can I avoid this clone ?
let kinds = p_kinds(&c.modifiers, env)?;
let class_kind = if kinds.has(modifier::ABSTRACT) {
ast::ClassishKind::CenumClass(ast::Abstraction::Abstract)
} else {
ast::ClassishKind::CenumClass(ast::Abstraction::Concrete)
};
// Helper to build X -> HH\MemberOf<enum_name, X>
let build_elt = |p: Pos, ty: ast::Hint| -> ast::Hint {
let enum_name = ast::Id(p.clone(), name_s.clone());
let enum_class = ast::Hint_::mk_happly(enum_name, vec![]);
let enum_class = ast::Hint::new(p.clone(), enum_class);
let elt_id = ast::Id(p.clone(), special_classes::MEMBER_OF.to_string());
let full_type = ast::Hint_::mk_happly(elt_id, vec![enum_class, ty]);
ast::Hint::new(p, full_type)
};
let extends = could_map(&c.extends_list, env, p_hint)?;
let mut enum_class = ast::Class_ {
annotation: (),
mode: env.file_mode(),
user_attributes,
file_attributes: vec![],
final_: false, // TODO(T77095784): support final EDTs
kind: class_kind,
is_xhp: false,
has_xhp_keyword: false,
name,
tparams: vec![],
extends: extends.clone(),
implements: vec![],
where_constraints: vec![],
consts: vec![],
namespace: mk_empty_ns_env(env),
span: p_pos(node, env),
enum_: Some(ast::Enum_ {
base: base_type,
constraint: None,
includes: extends,
}),
doc_comment: doc_comment_opt,
uses: vec![],
use_as_alias: vec![],
insteadof_alias: vec![],
xhp_attr_uses: vec![],
xhp_category: None,
reqs: vec![],
vars: vec![],
typeconsts: vec![],
methods: vec![],
attributes: vec![],
xhp_children: vec![],
xhp_attrs: vec![],
emit_id: None,
};
for n in c.elements.syntax_node_to_list_skip_separator() {
match &n.children {
// TODO(T77095784): check pos and span usage
EnumClassEnumerator(c) => {
// we turn:
// - type name = expression;
// into
// - const MemberOf<enum_name, type> name = expression
let name = pos_name(&c.name, env)?;
let pos = &name.0;
let kinds = p_kinds(&c.modifiers, env)?;
let has_abstract = kinds.has(modifier::ABSTRACT);
let elt_type = p_hint(&c.type_, env)?;
let full_type = build_elt(pos.clone(), elt_type);
let kind = if has_abstract {
ast::ClassConstKind::CCAbstract(None)
} else {
ast::ClassConstKind::CCConcrete(p_simple_initializer(
&c.initializer,
env,
)?)
};
let class_const = ast::ClassConst {
user_attributes: vec![],
type_: Some(full_type),
id: name,
kind,
doc_comment: None,
};
enum_class.consts.push(class_const)
}
_ => {
let pos = p_pos(n, env);
raise_parsing_error_pos(
&pos,
env,
&syntax_error::invalid_enum_class_enumerator,
)
}
}
}
Ok(vec![ast::Def::mk_class(enum_class)])
}
InclusionDirective(c) if env.file_mode() != file_info::Mode::Mhhi || env.codegen() => {
let expr = p_expr(&c.expression, env)?;
Ok(vec![ast::Def::mk_stmt(ast::Stmt::new(
p_pos(node, env),
ast::Stmt_::mk_expr(expr),
))])
}
NamespaceDeclaration(c) => {
let name = if let NamespaceDeclarationHeader(h) = &c.header.children {
&h.name
} else {
return missing_syntax("namespace_declaration_header", node, env);
};
let defs = match &c.body.children {
NamespaceBody(c) => {
let mut env1 = Env::clone_and_unset_toplevel_if_toplevel(env);
let env1 = env1.as_mut();
itertools::concat(
c.declarations
.syntax_node_to_list_skip_separator()
.map(|n| p_def(n, env1))
.collect::<Result<Vec<Vec<_>>, _>>()?,
)
}
_ => vec![],
};
Ok(vec![ast::Def::mk_namespace(pos_name(name, env)?, defs)])
}
NamespaceGroupUseDeclaration(c) => {
let uses: Result<Vec<_>, _> = c
.clauses
.syntax_node_to_list_skip_separator()
.map(|n| {
p_namespace_use_clause(
Some(&c.prefix),
p_namespace_use_kind(&c.kind, env),
n,
env,
)
})
.collect();
Ok(vec![ast::Def::mk_namespace_use(uses?)])
}
NamespaceUseDeclaration(c) => {
let uses: Result<Vec<_>, _> = c
.clauses
.syntax_node_to_list_skip_separator()
.map(|n| p_namespace_use_clause(None, p_namespace_use_kind(&c.kind, env), n, env))
.collect();
Ok(vec![ast::Def::mk_namespace_use(uses?)])
}
FileAttributeSpecification(_) => {
Ok(vec![ast::Def::mk_file_attributes(ast::FileAttribute {
user_attributes: p_user_attribute(node, env)?,
namespace: mk_empty_ns_env(env),
})])
}
ModuleDeclaration(md) => Ok(vec![ast::Def::mk_module(ast::ModuleDef {
annotation: (),
name: pos_name(&md.name, env)?,
user_attributes: p_user_attributes(&md.attribute_spec, env)?,
span: p_pos(node, env),
mode: env.file_mode(),
})]),
_ if env.file_mode() == file_info::Mode::Mhhi => Ok(vec![]),
_ => Ok(vec![ast::Def::mk_stmt(p_stmt(node, env)?)]),
}
}