Merge pull request #82 from ryangjchandler/feature/references-in-loops-and-functions

This commit is contained in:
Ryan Chandler 2022-09-15 00:50:02 +01:00 committed by GitHub
commit cddeff2901
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 266 additions and 12 deletions

View File

@ -35,6 +35,12 @@ impl From<&[u8]> for Identifier {
}
}
impl From<&str> for Identifier {
fn from(name: &str) -> Self {
Self::from(ByteString::from(name))
}
}
pub type ParamList = Vec<Param>;
#[derive(Debug, PartialEq, Clone)]
@ -44,6 +50,7 @@ pub struct Param {
pub variadic: bool,
pub default: Option<Expression>,
pub flag: Option<PropertyFlag>,
pub by_ref: bool,
}
impl From<ByteString> for Param {
@ -54,6 +61,7 @@ impl From<ByteString> for Param {
variadic: false,
default: None,
flag: None,
by_ref: false,
}
}
}
@ -225,6 +233,7 @@ pub enum Statement {
params: Vec<Param>,
body: Block,
return_type: Option<Type>,
by_ref: bool,
},
Class {
name: Identifier,
@ -251,6 +260,7 @@ pub enum Statement {
body: Block,
flags: Vec<MethodFlag>,
return_type: Option<Type>,
by_ref: bool,
},
If {
condition: Expression,
@ -443,11 +453,13 @@ pub enum Expression {
return_type: Option<Type>,
body: Block,
r#static: bool,
by_ref: bool,
},
ArrowFunction {
params: Vec<Param>,
return_type: Option<Type>,
expr: Box<Self>,
by_ref: bool,
},
New {
target: Box<Self>,

View File

@ -531,6 +531,7 @@ impl Parser {
body: vec![],
return_type,
flags: vec![MethodFlag::Public],
by_ref: false,
})
}
TokenKind::Function => {
@ -562,6 +563,7 @@ impl Parser {
body: vec![],
return_type,
flags: vec![],
by_ref: false,
})
}
_ => {
@ -934,8 +936,39 @@ impl Parser {
ret
}
}
TokenKind::Function if matches!(self.peek.kind, TokenKind::Identifier(_)) => {
self.function()?
TokenKind::Function
if matches!(
self.peek.kind,
TokenKind::Identifier(_) | 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 self.peek.kind == TokenKind::Ampersand {
let mut cloned = self.iter.clone();
for (index, _) in self.iter.clone().enumerate() {
if !matches!(
cloned.nth(index),
Some(Token {
kind: TokenKind::Identifier(_),
..
})
) {
let expr = self.expression(Precedence::Lowest)?;
self.semi()?;
return Ok(Statement::Expression { expr });
}
break;
}
self.function()?
} else {
self.function()?
}
}
TokenKind::SemiColon => {
self.next();
@ -1053,6 +1086,13 @@ impl Parser {
fn function(&mut self) -> ParseResult<Statement> {
self.next();
let by_ref = if self.current.kind == TokenKind::Ampersand {
self.next();
true
} else {
false
};
let name = self.ident()?;
self.lparen()?;
@ -1080,6 +1120,7 @@ impl Parser {
params,
body,
return_type,
by_ref,
})
}
@ -1264,6 +1305,13 @@ impl Parser {
if flags.contains(&TokenKind::Abstract) {
self.next();
let by_ref = if self.current.kind == TokenKind::Ampersand {
self.next();
true
} else {
false
};
let name = self.ident()?;
self.lparen()?;
@ -1290,6 +1338,7 @@ impl Parser {
body: vec![],
return_type,
flags: flags.iter().map(|t| t.clone().into()).collect(),
by_ref,
})
} else {
match self.function()? {
@ -1298,12 +1347,14 @@ impl Parser {
params,
body,
return_type,
by_ref,
} => Ok(Statement::Method {
name,
params,
body,
flags: flags.iter().map(|t| t.clone().into()).collect(),
return_type,
by_ref,
}),
_ => unreachable!(),
}
@ -1365,12 +1416,14 @@ impl Parser {
params,
body,
return_type,
by_ref,
} => Ok(Statement::Method {
name,
params,
body,
flags: vec![],
return_type,
by_ref,
}),
_ => unreachable!(),
},
@ -1588,12 +1641,14 @@ impl Parser {
uses,
return_type,
body,
by_ref,
..
} => Expression::Closure {
params,
uses,
return_type,
body,
by_ref,
r#static: true,
},
_ => unreachable!(),
@ -1602,6 +1657,13 @@ impl Parser {
TokenKind::Function => {
self.next();
let by_ref = if self.current.kind == TokenKind::Ampersand {
self.next();
true
} else {
false
};
self.lparen()?;
let params = self.param_list()?;
@ -1673,11 +1735,19 @@ impl Parser {
return_type,
body,
r#static: false,
by_ref,
}
}
TokenKind::Fn => {
self.next();
let by_ref = if self.current.kind == TokenKind::Ampersand {
self.next();
true
} else {
false
};
self.lparen()?;
let params = self.param_list()?;
@ -1700,6 +1770,7 @@ impl Parser {
params,
return_type,
expr: Box::new(value),
by_ref,
}
}
TokenKind::New => {
@ -2272,6 +2343,7 @@ mod tests {
.collect::<Vec<Param>>(),
body: $body.to_vec(),
return_type: None,
by_ref: false,
}
};
}
@ -2318,6 +2390,7 @@ mod tests {
flags: $flags.to_vec(),
body: $body.to_vec(),
return_type: None,
by_ref: false,
}
};
}
@ -2906,9 +2979,11 @@ mod tests {
variadic: false,
default: None,
flag: None,
by_ref: false,
}],
body: vec![],
return_type: None,
by_ref: false,
}],
);
}
@ -2925,9 +3000,11 @@ mod tests {
variadic: true,
default: None,
flag: None,
by_ref: false,
}],
body: vec![],
return_type: None,
by_ref: false,
}],
);
@ -2941,9 +3018,11 @@ mod tests {
variadic: true,
default: None,
flag: None,
by_ref: false,
}],
body: vec![],
return_type: None,
by_ref: false,
}],
);
@ -2958,6 +3037,7 @@ mod tests {
variadic: false,
default: None,
flag: None,
by_ref: false,
},
Param {
name: Expression::Variable { name: "baz".into() },
@ -2965,6 +3045,7 @@ mod tests {
variadic: false,
default: None,
flag: None,
by_ref: false,
},
Param {
name: Expression::Variable { name: "car".into() },
@ -2972,10 +3053,12 @@ mod tests {
variadic: true,
default: None,
flag: None,
by_ref: false,
},
],
body: vec![],
return_type: None,
by_ref: false,
}],
);
}
@ -2992,9 +3075,11 @@ mod tests {
variadic: false,
default: None,
flag: None,
by_ref: false,
}],
body: vec![],
return_type: None,
by_ref: false,
}],
);
}
@ -3011,9 +3096,11 @@ mod tests {
variadic: false,
default: None,
flag: None,
by_ref: false,
}],
body: vec![],
return_type: None,
by_ref: false,
}],
);
@ -3031,9 +3118,11 @@ mod tests {
variadic: false,
default: None,
flag: None,
by_ref: false,
}],
body: vec![],
return_type: None,
by_ref: false,
}],
);
}
@ -3050,9 +3139,11 @@ mod tests {
variadic: false,
default: None,
flag: None,
by_ref: false,
}],
body: vec![],
return_type: None,
by_ref: false,
}],
);
@ -3070,9 +3161,11 @@ mod tests {
variadic: false,
default: None,
flag: None,
by_ref: false,
}],
body: vec![],
return_type: None,
by_ref: false,
}],
);
}
@ -3086,6 +3179,7 @@ mod tests {
params: vec![],
body: vec![],
return_type: Some(Type::Plain("string".into())),
by_ref: false,
}],
);
@ -3096,6 +3190,7 @@ mod tests {
params: vec![],
body: vec![],
return_type: Some(Type::Void),
by_ref: false,
}],
);
}
@ -3177,7 +3272,8 @@ mod tests {
params: vec![],
body: vec![],
return_type: None,
flags: vec![MethodFlag::Public,]
flags: vec![MethodFlag::Public,],
by_ref: false,
}]
}),
args: vec![]
@ -3676,6 +3772,7 @@ mod tests {
params: vec![],
body: vec![],
return_type: None,
by_ref: false,
}],
}],
);
@ -3690,7 +3787,8 @@ mod tests {
uses: vec![],
return_type: None,
body: vec![],
r#static: false
r#static: false,
by_ref: false,
})],
);
}
@ -3702,7 +3800,8 @@ mod tests {
&[expr!(Expression::ArrowFunction {
params: vec![],
return_type: None,
expr: Box::new(Expression::Null)
expr: Box::new(Expression::Null),
by_ref: false,
})],
);
}
@ -3716,7 +3815,141 @@ mod tests {
uses: vec![],
return_type: None,
body: vec![],
r#static: true
r#static: true,
by_ref: false,
})],
);
}
#[test]
fn simple_foreach_reference() {
assert_ast(
"<?php foreach ($a as &$b) {}",
&[Statement::Foreach {
expr: Expression::Variable { name: "a".into() },
by_ref: true,
key_var: None,
value_var: Expression::Variable { name: "b".into() },
body: vec![],
}],
);
}
#[test]
fn key_value_foreach_reference() {
assert_ast(
"<?php foreach ($a as $b => &$c) {}",
&[Statement::Foreach {
expr: Expression::Variable { name: "a".into() },
by_ref: true,
key_var: Some(Expression::Variable { name: "b".into() }),
value_var: Expression::Variable { name: "c".into() },
body: vec![],
}],
);
}
#[test]
fn function_with_ref_param() {
assert_ast(
"<?php function a(&$b) {}",
&[Statement::Function {
name: "a".into(),
params: vec![Param {
name: Expression::Variable { name: "b".into() },
r#type: None,
variadic: false,
flag: None,
default: None,
by_ref: true,
}],
body: vec![],
return_type: None,
by_ref: false,
}],
);
}
#[test]
fn arrow_function_with_ref_param() {
assert_ast(
"<?php fn (&$b) => null;",
&[expr!(Expression::ArrowFunction {
params: vec![Param {
name: Expression::Variable { name: "b".into() },
r#type: None,
variadic: false,
flag: None,
default: None,
by_ref: true,
}],
return_type: None,
expr: Box::new(Expression::Null),
by_ref: false,
})],
);
}
#[test]
fn function_returning_ref() {
assert_ast(
"<?php function &a($b) {}",
&[Statement::Function {
name: "a".into(),
params: vec![Param {
name: Expression::Variable { name: "b".into() },
r#type: None,
variadic: false,
flag: None,
default: None,
by_ref: false,
}],
body: vec![],
return_type: None,
by_ref: true,
}],
);
}
#[test]
fn closure_returning_ref() {
assert_ast(
"<?php function &() {};",
&[expr!(Expression::Closure {
params: vec![],
body: vec![],
return_type: None,
r#static: false,
uses: vec![],
by_ref: true,
})],
);
}
#[test]
fn static_closures_returning_by_ref() {
assert_ast(
"<?php static function &() {};",
&[expr!(Expression::Closure {
params: vec![],
body: vec![],
return_type: None,
r#static: true,
uses: vec![],
by_ref: true,
})],
);
}
#[test]
fn arrow_functions_returning_by_ref() {
assert_ast(
"<?php fn &() => null;",
&[expr!(Expression::ArrowFunction {
params: vec![],
expr: Box::new(Expression::Null),
return_type: None,
by_ref: true,
})],
);
}

View File

@ -27,18 +27,26 @@ impl Parser {
// 1. If we don't see a variable, we should expect a type-string.
if !matches!(
self.current.kind,
TokenKind::Variable(_) | TokenKind::Ellipsis
TokenKind::Variable(_) | TokenKind::Ellipsis | TokenKind::Ampersand
) || self.config.force_type_strings
{
// 1a. Try to parse the type.
param_type = Some(self.type_string()?);
}
let variadic = if self.current.kind == TokenKind::Ellipsis {
self.next();
true
} else {
false
let mut variadic = false;
let mut by_ref = false;
match self.current.kind {
TokenKind::Ellipsis => {
self.next();
variadic = true;
}
TokenKind::Ampersand => {
self.next();
by_ref = true;
}
_ => {}
};
// 2. Then expect a variable.
@ -57,6 +65,7 @@ impl Parser {
variadic,
default,
flag,
by_ref,
});
self.optional_comma()?;