chore: allow soft reserved keywords to be used as function names (#185)

Signed-off-by: azjezz <azjezz@protonmail.com>
This commit is contained in:
Saif Eddin Gmati 2022-12-09 07:57:02 +01:00 committed by GitHub
parent d3612edc5c
commit 89866d952c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 572 additions and 50 deletions

View File

@ -244,11 +244,23 @@ expressions! {
functions::arrow_function(state)
})
#[before(list), current(TokenKind::Function)]
#[before(reserved_identifier_function_call), current(TokenKind::Function)]
anonymous_function(|state: &mut State| {
functions::anonymous_function(state)
})
#[before(list), current(
| TokenKind::True | TokenKind::False | TokenKind::Null
| TokenKind::Readonly | TokenKind::Self_ | TokenKind::Parent
| TokenKind::Enum | TokenKind::From
), peek(TokenKind::LeftParen)]
reserved_identifier_function_call(|state: &mut State| {
let ident = identifiers::ident_maybe_soft_reserved(state)?;
let lhs = Expression::Identifier(ident);
postfix(state, lhs, &TokenKind::LeftParen)
})
#[before(anonymous_class), current(TokenKind::List)]
list(|state: &mut State| {
arrays::list_expression(state)
@ -814,7 +826,9 @@ fn postfix(state: &mut State, lhs: Expression, op: &TokenKind) -> Result<Express
let property = match state.current.kind.clone() {
TokenKind::Dollar => variables::dynamic_variable(state)?,
TokenKind::Variable(_) => Expression::Variable(identifiers::var(state)?),
TokenKind::Identifier(_) => Expression::Identifier(identifiers::ident(state)?),
_ if identifiers::is_ident_maybe_reserved(&state.current.kind) => {
Expression::Identifier(identifiers::ident_maybe_reserved(state)?)
}
TokenKind::LeftBrace => {
must_be_method_call = true;
state.next();
@ -838,9 +852,6 @@ fn postfix(state: &mut State, lhs: Expression, op: &TokenKind) -> Result<Express
end,
})
}
_ if identifiers::is_reserved_ident(&state.current.kind) => {
Expression::Identifier(identifiers::ident_maybe_reserved(state)?)
}
_ => {
return expected_token_err!(["`{`", "`$`", "an identifier"], state);
}

View File

@ -6,7 +6,6 @@ use crate::parser::ast::functions::Closure;
use crate::parser::ast::functions::ClosureUse;
use crate::parser::ast::functions::Function;
use crate::parser::ast::functions::Method;
use crate::parser::ast::identifiers::Identifier;
use crate::parser::ast::modifiers::MethodModifierGroup;
use crate::parser::ast::Expression;
use crate::parser::ast::Statement;
@ -174,20 +173,7 @@ pub fn function(state: &mut State) -> ParseResult<Statement> {
false
};
let name = if state.current.kind == TokenKind::Null {
let start = state.current.span;
let end = (start.0, start.1 + 4);
state.next();
Identifier {
start,
name: "null".into(),
end,
}
} else {
identifiers::ident(state)?
};
let name = identifiers::ident_maybe_soft_reserved(state)?;
// get attributes before processing parameters, otherwise
// parameters will steal attributes of this function.

View File

@ -96,16 +96,56 @@ pub fn ident_maybe_reserved(state: &mut State) -> ParseResult<Identifier> {
}
}
pub fn is_reserved_ident(kind: &TokenKind) -> bool {
matches!(
kind,
TokenKind::Static
| TokenKind::Parent
| TokenKind::Self_
| TokenKind::Abstract
pub fn ident_maybe_soft_reserved(state: &mut State) -> ParseResult<Identifier> {
match state.current.kind {
_ if is_soft_reserved_ident(&state.current.kind) => {
let name = state.current.kind.to_string().into();
let start = state.current.span;
state.next();
let end = state.current.span;
Ok(Identifier { start, name, end })
}
_ => ident(state),
}
}
pub fn is_ident_maybe_soft_reserved(kind: &TokenKind) -> bool {
if let TokenKind::Identifier(_) = kind {
return true;
}
is_soft_reserved_ident(kind)
}
pub fn is_ident_maybe_reserved(kind: &TokenKind) -> bool {
if let TokenKind::Identifier(_) = kind {
return true;
}
is_reserved_ident(kind)
}
pub fn is_soft_reserved_ident(kind: &TokenKind) -> bool {
matches!(kind, |TokenKind::Parent| TokenKind::Self_
| TokenKind::True
| TokenKind::False
| TokenKind::Null
| TokenKind::Enum
| TokenKind::From
| TokenKind::Readonly)
}
pub fn is_reserved_ident(kind: &TokenKind) -> bool {
if is_soft_reserved_ident(kind) {
return true;
}
matches!(
kind,
TokenKind::Static
| TokenKind::Abstract
| TokenKind::Final
| TokenKind::For
| TokenKind::Private
@ -180,7 +220,5 @@ pub fn is_reserved_ident(kind: &TokenKind) -> bool {
| TokenKind::HaltCompiler
| TokenKind::Fn
| TokenKind::Match
| TokenKind::Enum
| TokenKind::From
)
}

View File

@ -247,8 +247,7 @@ pub fn args_list(state: &mut State) -> ParseResult<Vec<Arg>> {
while !state.is_eof() && state.current.kind != TokenKind::RightParen {
let mut name = None;
let mut unpack = false;
if (matches!(state.current.kind, TokenKind::Identifier(_))
|| identifiers::is_reserved_ident(&state.current.kind))
if identifiers::is_ident_maybe_reserved(&state.current.kind)
&& state.peek.kind == TokenKind::Colon
{
name = Some(identifiers::ident_maybe_reserved(state)?);

View File

@ -124,30 +124,31 @@ fn statement(state: &mut State) -> ParseResult<Statement> {
let statement = if has_attributes {
match &state.current.kind {
TokenKind::Abstract => classish::class_definition(state)?,
TokenKind::Readonly => classish::class_definition(state)?,
TokenKind::Readonly if state.peek.kind != TokenKind::LeftParen => {
classish::class_definition(state)?
}
TokenKind::Final => classish::class_definition(state)?,
TokenKind::Class => classish::class_definition(state)?,
TokenKind::Interface => classish::interface_definition(state)?,
TokenKind::Trait => classish::trait_definition(state)?,
TokenKind::Enum => classish::enum_definition(state)?,
TokenKind::Enum if state.peek.kind != TokenKind::LeftParen => {
classish::enum_definition(state)?
}
TokenKind::Function
if matches!(
state.peek.kind,
TokenKind::Identifier(_) | TokenKind::Null | TokenKind::Ampersand
) =>
if identifiers::is_ident_maybe_soft_reserved(&state.peek.kind)
|| state.peek.kind == TokenKind::Ampersand =>
{
// FIXME: This is incredibly hacky but we don't have a way to look at
// the next N tokens right now. We could probably do with a `peek_buf()`
// method like the Lexer has.
if state.peek.kind == TokenKind::Ampersand {
let mut cloned = state.iter.clone();
if let Some((index, _)) = state.iter.clone().enumerate().next() {
if let Some((_, token)) = state.iter.clone().enumerate().next() {
if !matches!(
cloned.nth(index),
Some(Token {
token,
Token {
kind: TokenKind::Identifier(_),
..
})
}
) {
let expr = expressions::lowest_precedence(state)?;
@ -173,17 +174,19 @@ fn statement(state: &mut State) -> ParseResult<Statement> {
} else {
match &state.current.kind {
TokenKind::Abstract => classish::class_definition(state)?,
TokenKind::Readonly => classish::class_definition(state)?,
TokenKind::Readonly if state.peek.kind != TokenKind::LeftParen => {
classish::class_definition(state)?
}
TokenKind::Final => classish::class_definition(state)?,
TokenKind::Class => classish::class_definition(state)?,
TokenKind::Interface => classish::interface_definition(state)?,
TokenKind::Trait => classish::trait_definition(state)?,
TokenKind::Enum => classish::enum_definition(state)?,
TokenKind::Enum if state.peek.kind != TokenKind::LeftParen => {
classish::enum_definition(state)?
}
TokenKind::Function
if matches!(
state.peek.kind,
TokenKind::Identifier(_) | TokenKind::Null | TokenKind::Ampersand
) =>
if identifiers::is_ident_maybe_soft_reserved(&state.peek.kind)
|| state.peek.kind == TokenKind::Ampersand =>
{
// FIXME: This is incredibly hacky but we don't have a way to look at
// the next N tokens right now. We could probably do with a `peek_buf()`

466
tests/fixtures/0278/ast.txt vendored Normal file
View File

@ -0,0 +1,466 @@
[
Function(
Function {
start: (
3,
1,
),
end: (
3,
24,
),
name: Identifier {
start: (
3,
10,
),
name: "true",
end: (
3,
14,
),
},
attributes: [],
parameters: FunctionParameterList {
start: (
3,
14,
),
end: (
3,
16,
),
members: [],
},
return_type: Some(
Void,
),
by_ref: false,
body: [],
},
),
Function(
Function {
start: (
4,
1,
),
end: (
4,
25,
),
name: Identifier {
start: (
4,
10,
),
name: "false",
end: (
4,
15,
),
},
attributes: [],
parameters: FunctionParameterList {
start: (
4,
15,
),
end: (
4,
17,
),
members: [],
},
return_type: Some(
Void,
),
by_ref: false,
body: [],
},
),
Function(
Function {
start: (
5,
1,
),
end: (
5,
24,
),
name: Identifier {
start: (
5,
10,
),
name: "null",
end: (
5,
14,
),
},
attributes: [],
parameters: FunctionParameterList {
start: (
5,
14,
),
end: (
5,
16,
),
members: [],
},
return_type: Some(
Void,
),
by_ref: false,
body: [],
},
),
Function(
Function {
start: (
6,
1,
),
end: (
6,
28,
),
name: Identifier {
start: (
6,
10,
),
name: "readonly",
end: (
6,
18,
),
},
attributes: [],
parameters: FunctionParameterList {
start: (
6,
18,
),
end: (
6,
20,
),
members: [],
},
return_type: Some(
Void,
),
by_ref: false,
body: [],
},
),
Function(
Function {
start: (
7,
1,
),
end: (
7,
24,
),
name: Identifier {
start: (
7,
10,
),
name: "self",
end: (
7,
14,
),
},
attributes: [],
parameters: FunctionParameterList {
start: (
7,
14,
),
end: (
7,
16,
),
members: [],
},
return_type: Some(
Void,
),
by_ref: false,
body: [],
},
),
Function(
Function {
start: (
8,
1,
),
end: (
8,
26,
),
name: Identifier {
start: (
8,
10,
),
name: "parent",
end: (
8,
16,
),
},
attributes: [],
parameters: FunctionParameterList {
start: (
8,
16,
),
end: (
8,
18,
),
members: [],
},
return_type: Some(
Void,
),
by_ref: false,
body: [],
},
),
Function(
Function {
start: (
9,
1,
),
end: (
9,
24,
),
name: Identifier {
start: (
9,
10,
),
name: "enum",
end: (
9,
14,
),
},
attributes: [],
parameters: FunctionParameterList {
start: (
9,
14,
),
end: (
9,
16,
),
members: [],
},
return_type: Some(
Void,
),
by_ref: false,
body: [],
},
),
Function(
Function {
start: (
10,
1,
),
end: (
10,
24,
),
name: Identifier {
start: (
10,
10,
),
name: "from",
end: (
10,
14,
),
},
attributes: [],
parameters: FunctionParameterList {
start: (
10,
14,
),
end: (
10,
16,
),
members: [],
},
return_type: Some(
Void,
),
by_ref: false,
body: [],
},
),
Expression {
expr: Call {
target: Identifier(
Identifier {
start: (
12,
1,
),
name: "true",
end: (
12,
5,
),
},
),
args: [],
},
},
Expression {
expr: Call {
target: Identifier(
Identifier {
start: (
13,
1,
),
name: "false",
end: (
13,
6,
),
},
),
args: [],
},
},
Expression {
expr: Call {
target: Identifier(
Identifier {
start: (
14,
1,
),
name: "null",
end: (
14,
5,
),
},
),
args: [],
},
},
Expression {
expr: Call {
target: Identifier(
Identifier {
start: (
15,
1,
),
name: "readonly",
end: (
15,
9,
),
},
),
args: [],
},
},
Expression {
expr: Call {
target: Identifier(
Identifier {
start: (
16,
1,
),
name: "self",
end: (
16,
5,
),
},
),
args: [],
},
},
Expression {
expr: Call {
target: Identifier(
Identifier {
start: (
17,
1,
),
name: "parent",
end: (
17,
7,
),
},
),
args: [],
},
},
Expression {
expr: Call {
target: Identifier(
Identifier {
start: (
18,
1,
),
name: "enum",
end: (
18,
5,
),
},
),
args: [],
},
},
Expression {
expr: Call {
target: Identifier(
Identifier {
start: (
19,
1,
),
name: "from",
end: (
19,
5,
),
},
),
args: [],
},
},
]

19
tests/fixtures/0278/code.php vendored Normal file
View File

@ -0,0 +1,19 @@
<?php
function true(): void {}
function false(): void {}
function null(): void {}
function readonly(): void {}
function self(): void {}
function parent(): void {}
function enum(): void {}
function from(): void {}
true();
false();
null();
readonly();
self();
parent();
enum();
from();