fn p_class_elt_, env: &mut Env) -> Result()

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(&param.name, '$');
                let p = &param.pos;
                let span = match &param.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(&param.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),
    }
}