Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/parser/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ impl Type {
_ => false,
}
}

pub fn is_bottom(&self) -> bool {
matches!(self, Type::Never | Type::Void)
}
}

impl Display for Type {
Expand Down
4 changes: 3 additions & 1 deletion src/parser/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub enum ParseError {
ReadonlyPropertyHasDefaultValue(String, String, Span),
MixingBracedAndUnBracedNamespaceDeclarations(Span),
NestedNamespaceDeclarations(Span),
ForbiddenTypeUsedInProperty(String, String, Type, Span),
}

impl Display for ParseError {
Expand Down Expand Up @@ -71,11 +72,12 @@ impl Display for ParseError {
Self::ConstructorInEnum(name, span) => write!(f, "Parse Error: Enum '{}' cannot have a constructor on line {} column {}", name, span.0, span.1),
Self::MissingCaseValueForBackedEnum(case, name, span) => write!(f, "Parse Error: Case `{}` of backed enum `{}` must have a value on line {} column {}", case, name, span.0, span.1),
Self::CaseValueForUnitEnum(case, name, span) => write!(f, "Parse Error: Case `{}` of unit enum `{}` must not have a value on line {} column {}", case, name, span.0, span.1),
Self::UnpredictableState(span) => write!(f, "Parse Error: Reached an unpredictable state on line {} column {}", span.0, span.1),
Self::StaticPropertyUsingReadonlyModifier(class, prop, span) => write!(f, "Parse Error: Static property {}:${} cannot be readonly on line {} column {}", class, prop, span.0, span.1),
Self::ReadonlyPropertyHasDefaultValue(class, prop, span) => write!(f, "Parse Error: Readonly property {}:${} cannot have a default value on line {} column {}", class, prop, span.0, span.1),
Self::MixingBracedAndUnBracedNamespaceDeclarations(span) => write!(f, "Parse Error: Cannot mix braced namespace declarations with unbraced namespace declarations on line {} column {}", span.0, span.1),
Self::NestedNamespaceDeclarations(span) => write!(f, "Parse Error: Namespace declarations cannot be mixed on line {} column {}", span.0, span.1),
Self::UnpredictableState(span) => write!(f, "Parse Error: Reached an unpredictable state on line {} column {}", span.0, span.1),
Self::ForbiddenTypeUsedInProperty(class, prop, ty, span) => write!(f, "Parse Error: Property {}::${} cannot have type `{}` on line {} column {}", class, prop, ty, span.0, span.1),
}
}
}
41 changes: 29 additions & 12 deletions src/parser/internal/classish_statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,30 +146,47 @@ impl Parser {
value = Some(self.expression(state, Precedence::Lowest)?);
}

self.semi(state)?;
let class_name: String = expected_scope!([
Scope::Class(name, _) => state.named(&name),
Scope::Trait(name) => state.named(&name),
Scope::AnonymousClass => state.named(&"class@anonymous".into()),
], state);

if flags.contains(&PropertyFlag::Readonly) {
if flags.contains(&PropertyFlag::Static) {
let class_name: String = expected_scope!([
Scope::Class(name, _) => state.named(&name),
Scope::Trait(name) => state.named(&name),
Scope::AnonymousClass => state.named(&"class@anonymous".into()),
], state);

return Err(ParseError::StaticPropertyUsingReadonlyModifier(class_name, var.to_string(), state.current.span));
}

if value.is_some() {
let class_name: String = expected_scope!([
Scope::Class(name, _) => state.named(&name),
Scope::Trait(name) => state.named(&name),
Scope::AnonymousClass => state.named(&"class@anonymous".into()),
], state);

return Err(ParseError::ReadonlyPropertyHasDefaultValue(class_name, var.to_string(), state.current.span));
}
}

match &ty {
Some(ty) => {
if ty.includes_callable() || ty.is_bottom() {
return Err(ParseError::ForbiddenTypeUsedInProperty(
class_name,
var.to_string(),
ty.clone(),
state.current.span,
));
}
}
None => {
if flags.contains(&PropertyFlag::Readonly) {
return Err(ParseError::MissingTypeForReadonlyProperty(
class_name,
var.to_string(),
state.current.span,
));
}
}
}

self.semi(state)?;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.semi moved here on purpose, this allows us to have to correct line/column in the error for the property, if we call it before returning the error, the line would be the next line.


Ok(Statement::Property {
var,
value,
Expand Down
72 changes: 40 additions & 32 deletions src/parser/internal/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,37 +53,13 @@ impl Parser {
};

while !state.is_eof() && state.current.kind != TokenKind::RightParen {
let mut param_type = None;

let flags: Vec<PropertyFlag> = self
.promoted_property_flags(state)?
.iter()
.map(|f| f.into())
.collect();

if !flags.is_empty() {
match construct {
0 => {
return Err(ParseError::PromotedPropertyOutsideConstructor(
state.current.span,
));
}
1 => {
return Err(ParseError::PromotedPropertyOnAbstractConstructor(
state.current.span,
));
}
_ => {}
}
}

if !matches!(
state.current.kind,
TokenKind::Variable(_) | TokenKind::Ellipsis | TokenKind::Ampersand
) {
// Try to parse the type.
param_type = Some(self.get_type(state)?);
}
let ty = self.get_optional_type(state)?;

let mut variadic = false;
let mut by_ref = false;
Expand All @@ -107,12 +83,42 @@ impl Parser {
TokenKind::Variable(v) => v
], state, "a varaible");

if flags.contains(&PropertyFlag::Readonly) && param_type.is_none() {
return Err(ParseError::MissingTypeForReadonlyProperty(
class_name,
var.to_string(),
state.current.span,
));
if !flags.is_empty() {
match construct {
0 => {
return Err(ParseError::PromotedPropertyOutsideConstructor(
state.current.span,
));
}
1 => {
return Err(ParseError::PromotedPropertyOnAbstractConstructor(
state.current.span,
));
}
_ => {}
}

match &ty {
Some(ty) => {
if ty.includes_callable() || ty.is_bottom() {
return Err(ParseError::ForbiddenTypeUsedInProperty(
class_name,
var.to_string(),
ty.clone(),
state.current.span,
));
}
}
None => {
if flags.contains(&PropertyFlag::Readonly) {
return Err(ParseError::MissingTypeForReadonlyProperty(
class_name,
var.to_string(),
state.current.span,
));
}
}
}
}

let mut default = None;
Expand All @@ -123,13 +129,15 @@ impl Parser {

params.push(Param {
name: Expression::Variable { name: var },
r#type: param_type,
r#type: ty,
variadic,
default,
flags,
by_ref,
});

// TODO: bug! this allows `function foo(string $a ...$b &$c) {}`
// TODO: if `,` is found, look for next param, otherwise break out of the loop.
self.optional_comma(state)?;
}

Expand Down
35 changes: 13 additions & 22 deletions src/parser/internal/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,14 @@ impl Parser {
&self,
state: &mut State,
) -> ParseResult<Option<Type>> {
let ty = self.maybe_optional_nullable(state, &|state| {
self.maybe_optional_static(state, &|state| self.get_optional_simple_type(state))
});
if state.current.kind == TokenKind::Question {
return Ok(Some(self.get_type(state)?));
}

let ty = self.maybe_optional_static(state, &|state| self.get_optional_simple_type(state));

match ty {
Some(ty) => {
if ty.nullable() {
return Ok(Some(ty));
}

if state.current.kind == TokenKind::Pipe {
state.next();

Expand Down Expand Up @@ -271,8 +269,15 @@ impl Parser {
) -> 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(otherwise(state)?)))
Ok(Type::Nullable(Box::new(inner)))
} else {
otherwise(state)
}
Expand Down Expand Up @@ -310,20 +315,6 @@ impl Parser {
otherwise(state)
}

fn maybe_optional_nullable(
&self,
state: &mut State,
otherwise: &(dyn Fn(&mut State) -> Option<Type>),
) -> Option<Type> {
if state.current.kind == TokenKind::Question {
state.next();

Some(Type::Nullable(Box::new(otherwise(state)?)))
} else {
otherwise(state)
}
}

fn maybe_optional_static(
&self,
state: &mut State,
Expand Down
21 changes: 0 additions & 21 deletions tests/0086/ast.txt

This file was deleted.

1 change: 1 addition & 0 deletions tests/0086/parser-error.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MissingTypeForReadonlyProperty("Foo", "bar", (1, 39)) -> Parse Error: Readonly property Foo::$bar must have type on line 1 column 39
2 changes: 1 addition & 1 deletion tests/0113/parser-error.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
PromotedPropertyOutsideConstructor((5, 16)) -> Parse Error: Cannot declare promoted property outside a constructor on line 5 column 16
PromotedPropertyOutsideConstructor((5, 25)) -> Parse Error: Cannot declare promoted property outside a constructor on line 5 column 25
2 changes: 1 addition & 1 deletion tests/0114/parser-error.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
PromotedPropertyOnAbstractConstructor((5, 16)) -> Parse Error: Cannot declare promoted property in an abstract constructor on line 5 column 16
PromotedPropertyOnAbstractConstructor((5, 25)) -> Parse Error: Cannot declare promoted property in an abstract constructor on line 5 column 25
2 changes: 1 addition & 1 deletion tests/0115/parser-error.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
PromotedPropertyOnAbstractConstructor((5, 16)) -> Parse Error: Cannot declare promoted property in an abstract constructor on line 5 column 16
PromotedPropertyOnAbstractConstructor((5, 25)) -> Parse Error: Cannot declare promoted property in an abstract constructor on line 5 column 25
2 changes: 1 addition & 1 deletion tests/0116/parser-error.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
PromotedPropertyOnAbstractConstructor((5, 16)) -> Parse Error: Cannot declare promoted property in an abstract constructor on line 5 column 16
PromotedPropertyOnAbstractConstructor((5, 25)) -> Parse Error: Cannot declare promoted property in an abstract constructor on line 5 column 25
2 changes: 1 addition & 1 deletion tests/0154/parser-error.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
StaticPropertyUsingReadonlyModifier("Foo\\Bar\\Baz", "foo", (7, 1)) -> Parse Error: Static property Foo\Bar\Baz:$foo cannot be readonly on line 7 column 1
StaticPropertyUsingReadonlyModifier("Foo\\Bar\\Baz", "foo", (6, 40)) -> Parse Error: Static property Foo\Bar\Baz:$foo cannot be readonly on line 6 column 40
2 changes: 1 addition & 1 deletion tests/0156/parser-error.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ReadonlyPropertyHasDefaultValue("Foo\\Bar\\Baz", "foo", (7, 1)) -> Parse Error: Readonly property Foo\Bar\Baz:$foo cannot have a default value on line 7 column 1
ReadonlyPropertyHasDefaultValue("Foo\\Bar\\Baz", "foo", (6, 41)) -> Parse Error: Readonly property Foo\Bar\Baz:$foo cannot have a default value on line 6 column 41
7 changes: 7 additions & 0 deletions tests/0165/0166/code.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

class Foo {
public function __construct(
public void $s,
) {}
}
1 change: 1 addition & 0 deletions tests/0165/0166/parser-error.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ForbiddenTypeUsedInProperty("Foo", "s", Void, (5, 23)) -> Parse Error: Property Foo::$s cannot have type `void` on line 5 column 23
Loading