use php_ast::*;
use php_lexer::TokenKind;
use crate::diagnostics::{ForbiddenRule, ParseError};
use crate::parser::Parser;
use crate::version::PhpVersion;
pub(super) fn parse_trait_adaptations<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
) -> ArenaVec<'arena, TraitAdaptation<'arena, 'src>> {
let mut adaptations = parser.alloc_vec();
while !parser.check(TokenKind::RightBrace) && !parser.check(TokenKind::Eof) {
let start = parser.start_span();
let first_name = if parser.check(TokenKind::Namespace)
&& parser.peek_kind() != Some(TokenKind::Backslash)
{
let value = parser.current_text();
let span = parser.advance().span;
Name::Simple { value, span }
} else {
parser.parse_name()
};
if parser.eat(TokenKind::DoubleColon).is_some() {
super::validate_class_ref(parser, &first_name, "trait");
let method = if let Some((text, span)) = parser.eat_identifier_or_keyword() {
Name::Simple { value: text, span }
} else {
let span = parser.current_span();
parser.error(ParseError::Expected {
expected: "method name".into(),
found: parser.current_kind().description().into(),
span,
});
Name::Error { span }
};
if parser.check(TokenKind::Identifier)
&& parser.current_text().eq_ignore_ascii_case("insteadof")
{
parser.advance(); let mut insteadof = {
let mut _v = parser.alloc_vec_with_capacity(1);
_v.push(parser.parse_name());
_v
};
while parser.eat(TokenKind::Comma).is_some() {
if parser.check(TokenKind::Semicolon) {
parser.trailing_comma_error("trait name");
break;
}
insteadof.push(parser.parse_name());
}
for name in insteadof.iter() {
super::validate_class_ref(parser, name, "trait");
}
parser.expect_member_end();
let span = Span::new(start, parser.previous_end());
adaptations.push(TraitAdaptation {
kind: TraitAdaptationKind::Precedence {
trait_name: first_name,
method,
insteadof,
},
span,
});
} else if parser.eat(TokenKind::As).is_some() {
let (new_modifier, is_final, new_name) = parse_alias_rhs(parser);
parser.expect_member_end();
let span = Span::new(start, parser.previous_end());
adaptations.push(TraitAdaptation {
kind: TraitAdaptationKind::Alias {
trait_name: Some(first_name),
method,
new_modifier,
is_final,
new_name,
},
span,
});
} else {
let span = parser.current_span();
parser.error(ParseError::Expected {
expected: "'insteadof' or 'as'".into(),
found: parser.current_kind().description().into(),
span,
});
parser.advance();
}
} else if parser.eat(TokenKind::As).is_some() {
let (new_modifier, is_final, new_name) = parse_alias_rhs(parser);
parser.expect_member_end();
let span = Span::new(start, parser.previous_end());
adaptations.push(TraitAdaptation {
kind: TraitAdaptationKind::Alias {
trait_name: None,
method: first_name,
new_modifier,
is_final,
new_name,
},
span,
});
} else {
let span = parser.current_span();
parser.error(ParseError::Expected {
expected: "'::' or 'as'".into(),
found: parser.current_kind().description().into(),
span,
});
parser.advance();
}
}
parser.expect(TokenKind::RightBrace);
adaptations
}
fn parse_alias_rhs<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
) -> (Option<Visibility>, bool, Option<Name<'arena, 'src>>) {
let is_final = parser.check(TokenKind::Final);
if is_final {
let span = parser.advance().span;
parser.require_version(PhpVersion::Php83, "final trait method alias", span);
}
let new_modifier = match parser.current_kind() {
_ if is_final => None,
TokenKind::Public => {
parser.advance();
Some(Visibility::Public)
}
TokenKind::Protected => {
parser.advance();
Some(Visibility::Protected)
}
TokenKind::Private => {
parser.advance();
Some(Visibility::Private)
}
_ => None,
};
let mut has_modifier = is_final || new_modifier.is_some();
if !has_modifier {
let error = match parser.current_kind() {
TokenKind::Static | TokenKind::Abstract => Some((
ForbiddenRule::InvalidTraitAliasModifier,
format!(
"Cannot use \"{}\" as method modifier in trait alias",
parser.current_text().to_ascii_lowercase()
),
)),
TokenKind::Readonly => Some((
ForbiddenRule::ModifierNotAllowed,
"Cannot use the readonly modifier on a method".into(),
)),
_ => None,
};
if let Some((rule, message)) = error {
let span = parser.advance().span;
parser.forbid(rule, message, span);
has_modifier = true;
}
}
let new_name = parser
.eat_identifier_or_keyword()
.map(|(text, span)| Name::Simple { value: text, span });
if new_name.is_none() && !has_modifier {
parser.error_expected("identifier");
}
(new_modifier, is_final, new_name)
}