fix improve dnf types parser

Signed-off-by: azjezz <azjezz@protonmail.com>
This commit is contained in:
azjezz 2022-12-05 19:46:21 +01:00
parent e23c80a3a3
commit f92b8c4c73
No known key found for this signature in database
GPG Key ID: B00E0A46B3F1C157
12 changed files with 849 additions and 37 deletions

View File

@ -36,6 +36,7 @@ pub enum ParseError {
MatchExpressionWithMultipleDefaultArms(Span),
CannotFindTypeInCurrentScope(String, Span),
ExpectedItemDefinitionAfterAttributes(Span),
NestedDisjunctiveNormalFormTypes(Span),
}
impl Display for ParseError {
@ -84,6 +85,7 @@ impl Display for ParseError {
Self::MatchExpressionWithMultipleDefaultArms(span) => write!(f, "Parse Error: Match expressions may only contain one default arm on line {} column {}", span.0, span.1),
Self::CannotFindTypeInCurrentScope(ty, span) => write!(f, "Parse Error: Cannot find type `{}` in this scope on line {} on column {}", ty, span.0, span.1),
Self::ExpectedItemDefinitionAfterAttributes(span) => write!(f, "Parse Error: Expected item definition after attribute on line {} column {}", span.0, span.1),
Self::NestedDisjunctiveNormalFormTypes(span) => write!(f, "Parse Error: Nested disjunctive normal form types are not allowed on line {} column {}", span.0, span.1),
}
}
}

View File

@ -8,6 +8,7 @@ use crate::parser::error::ParseError;
use crate::parser::error::ParseResult;
use crate::parser::state::State;
use crate::parser::Parser;
use crate::peek_token;
impl Parser {
pub(in crate::parser) fn try_block_caught_type_string(
@ -39,20 +40,42 @@ impl Parser {
}
pub(in crate::parser) fn get_type(&self, state: &mut State) -> ParseResult<Type> {
let ty = self.maybe_nullable(state, &|state| self.get_simple_type(state))?;
if ty.nullable() {
return Ok(ty);
if state.current.kind == TokenKind::Question {
return self.get_nullable_type(state);
}
// (A|B|..)&C.. or (A&B&..)|C..
if state.current.kind == TokenKind::LeftParen {
state.next();
let ty = self.get_simple_type(state)?;
return peek_token!([
TokenKind::Pipe => {
let union = self.get_union_type(state, ty, true)?;
self.rparen(state)?;
self.get_intersection_type(state, union, false)
},
TokenKind::Ampersand => {
let intersection = self.get_intersection_type(state, ty, true)?;
self.rparen(state)?;
self.get_union_type(state, intersection, false)
},
], state, ["`|`", "`&`"]);
}
let ty = self.get_simple_type(state)?;
if state.current.kind == TokenKind::Pipe {
return self.parse_union(state, ty);
return self.get_union_type(state, ty, false);
}
if state.current.kind == TokenKind::Ampersand
&& !matches!(state.peek.kind, TokenKind::Variable(_))
{
return self.parse_intersection(state, ty);
return self.get_intersection_type(state, ty, false);
}
Ok(ty)
@ -63,7 +86,29 @@ impl Parser {
state: &mut State,
) -> ParseResult<Option<Type>> {
if state.current.kind == TokenKind::Question {
return Ok(Some(self.get_type(state)?));
return self.get_nullable_type(state).map(Some);
}
// (A|B|..)&C.. or (A&B&..)|C..
if state.current.kind == TokenKind::LeftParen {
state.next();
let ty = self.get_simple_type(state)?;
return peek_token!([
TokenKind::Pipe => {
let union = self.get_union_type(state, ty, true)?;
self.rparen(state)?;
self.get_intersection_type(state, union, false).map(Some)
},
TokenKind::Ampersand => {
let intersection = self.get_intersection_type(state, ty, true)?;
self.rparen(state)?;
self.get_union_type(state, intersection, false).map(Some)
},
], state, ["`|`", "`&`"]);
}
let ty = self.get_optional_simple_type(state)?;
@ -71,13 +116,13 @@ impl Parser {
match ty {
Some(ty) => {
if state.current.kind == TokenKind::Pipe {
return Ok(Some(self.parse_union(state, ty)?));
return Ok(Some(self.get_union_type(state, ty, false)?));
}
if state.current.kind == TokenKind::Ampersand
&& !matches!(state.peek.kind, TokenKind::Variable(_))
{
return Ok(Some(self.parse_intersection(state, ty)?));
return Ok(Some(self.get_intersection_type(state, ty, false)?));
}
Ok(Some(ty))
@ -86,6 +131,21 @@ impl Parser {
}
}
fn get_nullable_type(&self, state: &mut State) -> ParseResult<Type> {
state.next();
let ty = self.get_simple_type(state)?;
if ty.standalone() {
return Err(ParseError::StandaloneTypeUsedInCombination(
ty,
state.current.span,
));
}
Ok(Type::Nullable(Box::new(ty)))
}
fn get_optional_simple_type(&self, state: &mut State) -> ParseResult<Option<Type>> {
match state.current.kind.clone() {
TokenKind::Array => {
@ -190,7 +250,12 @@ impl Parser {
.ok_or_else(|| expected_token!(["a type"], state))
}
fn parse_union(&self, state: &mut State, other: Type) -> ParseResult<Type> {
fn get_union_type(
&self,
state: &mut State,
other: Type,
within_dnf: bool,
) -> ParseResult<Type> {
if other.standalone() {
return Err(ParseError::StandaloneTypeUsedInCombination(
other,
@ -203,10 +268,29 @@ impl Parser {
expect_token!([TokenKind::Pipe], state, ["|"]);
loop {
let ty = if state.current.kind == TokenKind::LeftParen {
if within_dnf {
// don't allow nesting.
//
// examples on how we got here:
//
// v-- get_intersection_type: within_dnf = fasle
// v-- get_union_type: within_dnf = true
// v-- error
// F&(A|(D&S))
//
// v-- get_intersection_type: within_dnf = fasle
// v-- get_union_type: within_dnf = true
// v-- error
// F&(A|B|(D&S))
return Err(ParseError::NestedDisjunctiveNormalFormTypes(
state.current.span,
));
}
state.next();
let other = self.get_simple_type(state)?;
let ty = self.parse_intersection(state, other)?;
let ty = self.get_intersection_type(state, other, true)?;
self.rparen(state)?;
@ -235,7 +319,12 @@ impl Parser {
Ok(Type::Union(types))
}
fn parse_intersection(&self, state: &mut State, other: Type) -> ParseResult<Type> {
fn get_intersection_type(
&self,
state: &mut State,
other: Type,
within_dnf: bool,
) -> ParseResult<Type> {
if other.standalone() {
return Err(ParseError::StandaloneTypeUsedInCombination(
other,
@ -248,10 +337,29 @@ impl Parser {
expect_token!([TokenKind::Ampersand], state, ["&"]);
loop {
let ty = if state.current.kind == TokenKind::LeftParen {
if within_dnf {
// don't allow nesting.
//
// examples on how we got here:
//
// v-- get_union_type: within_dnf = fasle
// v-- get_intersection_type: within_dnf = true
// v-- error
// F|(A&(D|S))
//
// v-- get_union_type: within_dnf = fasle
// v-- get_intersection_type: within_dnf = true
// v-- error
// F|(A&B&(D|S))
return Err(ParseError::NestedDisjunctiveNormalFormTypes(
state.current.span,
));
}
state.next();
let other = self.get_simple_type(state)?;
let ty = self.parse_union(state, other)?;
let ty = self.get_union_type(state, other, true)?;
self.rparen(state)?;
@ -281,25 +389,4 @@ impl Parser {
Ok(Type::Intersection(types))
}
fn maybe_nullable(
&self,
state: &mut State,
otherwise: &(dyn Fn(&mut State) -> ParseResult<Type>),
) -> ParseResult<Type> {
if state.current.kind == TokenKind::Question {
state.next();
let inner = otherwise(state)?;
if inner.standalone() {
return Err(ParseError::StandaloneTypeUsedInCombination(
inner,
state.current.span,
));
}
Ok(Type::Nullable(Box::new(inner)))
} else {
otherwise(state)
}
}
}

154
tests/fixtures/0251/ast.txt vendored Normal file
View File

@ -0,0 +1,154 @@
[
Function {
name: Identifier {
start: (
3,
10,
),
name: "bar",
end: (
3,
13,
),
},
attributes: [],
params: [
Param {
name: Variable {
start: (
4,
13,
),
name: "i",
end: (
5,
1,
),
},
attributes: [],
type: Some(
Union(
[
Identifier(
Identifier {
start: (
4,
5,
),
name: "A",
end: (
4,
6,
),
},
),
Intersection(
[
Identifier(
Identifier {
start: (
4,
8,
),
name: "B",
end: (
4,
9,
),
},
),
Identifier(
Identifier {
start: (
4,
10,
),
name: "C",
end: (
4,
11,
),
},
),
],
),
],
),
),
variadic: false,
default: None,
flags: [],
by_ref: false,
},
],
body: [
Return {
value: Some(
Variable(
Variable {
start: (
6,
12,
),
name: "i",
end: (
6,
14,
),
},
),
),
},
],
return_type: Some(
Union(
[
Intersection(
[
Identifier(
Identifier {
start: (
5,
5,
),
name: "B",
end: (
5,
6,
),
},
),
Identifier(
Identifier {
start: (
5,
7,
),
name: "C",
end: (
5,
8,
),
},
),
],
),
Identifier(
Identifier {
start: (
5,
10,
),
name: "A",
end: (
5,
12,
),
},
),
],
),
),
by_ref: false,
},
]

7
tests/fixtures/0251/code.php vendored Normal file
View File

@ -0,0 +1,7 @@
<?php
function bar(
A|(B&C) $i
): (B&C)|A {
return $i;
}

204
tests/fixtures/0251/tokens.txt vendored Normal file
View File

@ -0,0 +1,204 @@
[
Token {
kind: OpenTag(
Full,
),
span: (
1,
1,
),
},
Token {
kind: Function,
span: (
3,
1,
),
},
Token {
kind: Identifier(
"bar",
),
span: (
3,
10,
),
},
Token {
kind: LeftParen,
span: (
3,
13,
),
},
Token {
kind: Identifier(
"A",
),
span: (
4,
5,
),
},
Token {
kind: Pipe,
span: (
4,
6,
),
},
Token {
kind: LeftParen,
span: (
4,
7,
),
},
Token {
kind: Identifier(
"B",
),
span: (
4,
8,
),
},
Token {
kind: Ampersand,
span: (
4,
9,
),
},
Token {
kind: Identifier(
"C",
),
span: (
4,
10,
),
},
Token {
kind: RightParen,
span: (
4,
11,
),
},
Token {
kind: Variable(
"i",
),
span: (
4,
13,
),
},
Token {
kind: RightParen,
span: (
5,
1,
),
},
Token {
kind: Colon,
span: (
5,
2,
),
},
Token {
kind: LeftParen,
span: (
5,
4,
),
},
Token {
kind: Identifier(
"B",
),
span: (
5,
5,
),
},
Token {
kind: Ampersand,
span: (
5,
6,
),
},
Token {
kind: Identifier(
"C",
),
span: (
5,
7,
),
},
Token {
kind: RightParen,
span: (
5,
8,
),
},
Token {
kind: Pipe,
span: (
5,
9,
),
},
Token {
kind: Identifier(
"A",
),
span: (
5,
10,
),
},
Token {
kind: LeftBrace,
span: (
5,
12,
),
},
Token {
kind: Return,
span: (
6,
5,
),
},
Token {
kind: Variable(
"i",
),
span: (
6,
12,
),
},
Token {
kind: SemiColon,
span: (
6,
14,
),
},
Token {
kind: RightBrace,
span: (
7,
1,
),
},
]

7
tests/fixtures/0252/code.php vendored Normal file
View File

@ -0,0 +1,7 @@
<?php
function bar(
F|(A&(B|C)) $i
) {
return $i;
}

1
tests/fixtures/0252/parser-error.txt vendored Normal file
View File

@ -0,0 +1 @@
NestedDisjunctiveNormalFormTypes((4, 10)) -> Parse Error: Nested disjunctive normal form types are not allowed on line 4 column 10

172
tests/fixtures/0252/tokens.txt vendored Normal file
View File

@ -0,0 +1,172 @@
[
Token {
kind: OpenTag(
Full,
),
span: (
1,
1,
),
},
Token {
kind: Function,
span: (
3,
1,
),
},
Token {
kind: Identifier(
"bar",
),
span: (
3,
10,
),
},
Token {
kind: LeftParen,
span: (
3,
13,
),
},
Token {
kind: Identifier(
"F",
),
span: (
4,
5,
),
},
Token {
kind: Pipe,
span: (
4,
6,
),
},
Token {
kind: LeftParen,
span: (
4,
7,
),
},
Token {
kind: Identifier(
"A",
),
span: (
4,
8,
),
},
Token {
kind: Ampersand,
span: (
4,
9,
),
},
Token {
kind: LeftParen,
span: (
4,
10,
),
},
Token {
kind: Identifier(
"B",
),
span: (
4,
11,
),
},
Token {
kind: Pipe,
span: (
4,
12,
),
},
Token {
kind: Identifier(
"C",
),
span: (
4,
13,
),
},
Token {
kind: RightParen,
span: (
4,
14,
),
},
Token {
kind: RightParen,
span: (
4,
15,
),
},
Token {
kind: Variable(
"i",
),
span: (
4,
17,
),
},
Token {
kind: RightParen,
span: (
5,
1,
),
},
Token {
kind: LeftBrace,
span: (
5,
3,
),
},
Token {
kind: Return,
span: (
6,
5,
),
},
Token {
kind: Variable(
"i",
),
span: (
6,
12,
),
},
Token {
kind: SemiColon,
span: (
6,
14,
),
},
Token {
kind: RightBrace,
span: (
7,
1,
),
},
]

7
tests/fixtures/0253/code.php vendored Normal file
View File

@ -0,0 +1,7 @@
<?php
function bar(
F&(A|(B&C)) $i
) {
return $i;
}

1
tests/fixtures/0253/parser-error.txt vendored Normal file
View File

@ -0,0 +1 @@
NestedDisjunctiveNormalFormTypes((4, 10)) -> Parse Error: Nested disjunctive normal form types are not allowed on line 4 column 10

172
tests/fixtures/0253/tokens.txt vendored Normal file
View File

@ -0,0 +1,172 @@
[
Token {
kind: OpenTag(
Full,
),
span: (
1,
1,
),
},
Token {
kind: Function,
span: (
3,
1,
),
},
Token {
kind: Identifier(
"bar",
),
span: (
3,
10,
),
},
Token {
kind: LeftParen,
span: (
3,
13,
),
},
Token {
kind: Identifier(
"F",
),
span: (
4,
5,
),
},
Token {
kind: Ampersand,
span: (
4,
6,
),
},
Token {
kind: LeftParen,
span: (
4,
7,
),
},
Token {
kind: Identifier(
"A",
),
span: (
4,
8,
),
},
Token {
kind: Pipe,
span: (
4,
9,
),
},
Token {
kind: LeftParen,
span: (
4,
10,
),
},
Token {
kind: Identifier(
"B",
),
span: (
4,
11,
),
},
Token {
kind: Ampersand,
span: (
4,
12,
),
},
Token {
kind: Identifier(
"C",
),
span: (
4,
13,
),
},
Token {
kind: RightParen,
span: (
4,
14,
),
},
Token {
kind: RightParen,
span: (
4,
15,
),
},
Token {
kind: Variable(
"i",
),
span: (
4,
17,
),
},
Token {
kind: RightParen,
span: (
5,
1,
),
},
Token {
kind: LeftBrace,
span: (
5,
3,
),
},
Token {
kind: Return,
span: (
6,
5,
),
},
Token {
kind: Variable(
"i",
),
span: (
6,
12,
),
},
Token {
kind: SemiColon,
span: (
6,
14,
),
},
Token {
kind: RightBrace,
span: (
7,
1,
),
},
]

View File

@ -41,15 +41,13 @@ fn third_party_3_symfony_framework() {
// FIXME: Remove this one once I've found the energy to sort out heredocs / nowdocs.
"src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php",
"src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php",
// FIXME: Remove this once we can support (A&B)|C DNF types.
"src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/compositetype_classes.php",
// FIXME: Remove these once we can support arbitrary opening and closing tags.
"src/Symfony/Component/ErrorHandler/Resources/views/exception.html.php",
"src/Symfony/Component/ErrorHandler/Resources/views/exception_full.html.php",
"src/Symfony/Component/ErrorHandler/Resources/views/logs.html.php",
"src/Symfony/Component/ErrorHandler/Resources/views/trace.html.php",
"src/Symfony/Component/ErrorHandler/Resources/views/traces.html.php",
"src/Symfony/Component/ErrorHandler/Resources/views/traces_text.html.php"
"src/Symfony/Component/ErrorHandler/Resources/views/traces_text.html.php",
],
);
}