diff --git a/src/parser/error.rs b/src/parser/error.rs index 90e41d6c..2b8264ef 100644 --- a/src/parser/error.rs +++ b/src/parser/error.rs @@ -34,6 +34,7 @@ pub enum ParseError { NestedNamespaceDeclarations(Span), ForbiddenTypeUsedInProperty(String, String, Type, Span), MatchExpressionWithMultipleDefaultArms(Span), + CannotFindTypeInCurrentScope(String, Span), } impl Display for ParseError { @@ -80,6 +81,7 @@ impl Display for ParseError { 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), 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), } } } diff --git a/src/parser/internal/classish.rs b/src/parser/internal/classish.rs index bc6143a8..55622af7 100644 --- a/src/parser/internal/classish.rs +++ b/src/parser/internal/classish.rs @@ -21,47 +21,53 @@ impl Parser { let name = self.ident(state)?; - scoped!(state, Scope::Class(name.clone(), flags.clone()), { - let mut extends: Option = None; + let mut has_parent = false; + let mut extends: Option = None; - if state.current.kind == TokenKind::Extends { - state.next(); - extends = Some(self.full_name(state)?.into()); - } + if state.current.kind == TokenKind::Extends { + state.next(); + extends = Some(self.full_name(state)?.into()); + has_parent = true; + } - let implements = if state.current.kind == TokenKind::Implements { - state.next(); + scoped!( + state, + Scope::Class(name.clone(), flags.clone(), has_parent), + { + let implements = if state.current.kind == TokenKind::Implements { + state.next(); - self.at_least_one_comma_separated::(state, &|parser, state| { - Ok(parser.full_name(state)?.into()) - })? - } else { - Vec::new() - }; + self.at_least_one_comma_separated::(state, &|parser, state| { + Ok(parser.full_name(state)?.into()) + })? + } else { + Vec::new() + }; - self.lbrace(state)?; + self.lbrace(state)?; - let mut body = Vec::new(); - while state.current.kind != TokenKind::RightBrace { - state.gather_comments(); + let mut body = Vec::new(); + while state.current.kind != TokenKind::RightBrace { + state.gather_comments(); - if state.current.kind == TokenKind::RightBrace { - state.clear_comments(); - break; + if state.current.kind == TokenKind::RightBrace { + state.clear_comments(); + break; + } + + body.push(self.class_like_statement(state)?); } + self.rbrace(state)?; - body.push(self.class_like_statement(state)?); + Ok(Statement::Class { + name: name.into(), + extends, + implements, + body, + flags, + }) } - self.rbrace(state)?; - - Ok(Statement::Class { - name: name.into(), - extends, - implements, - body, - flags, - }) - }) + ) } pub(in crate::parser) fn interface_definition( @@ -140,20 +146,22 @@ impl Parser { expect_token!([TokenKind::New], state, ["`new`"]); expect_token!([TokenKind::Class], state, ["`class`"]); - scoped!(state, Scope::AnonymousClass, { - let mut args = vec![]; + let mut args = vec![]; - if state.current.kind == TokenKind::LeftParen { - args = self.args_list(state)?; - } + if state.current.kind == TokenKind::LeftParen { + args = self.args_list(state)?; + } - let mut extends: Option = None; + let mut has_parent = false; + let mut extends: Option = None; - if state.current.kind == TokenKind::Extends { - state.next(); - extends = Some(self.full_name(state)?.into()); - } + if state.current.kind == TokenKind::Extends { + state.next(); + extends = Some(self.full_name(state)?.into()); + has_parent = true; + } + scoped!(state, Scope::AnonymousClass(has_parent), { let mut implements = Vec::new(); if state.current.kind == TokenKind::Implements { state.next(); diff --git a/src/parser/internal/classish_statement.rs b/src/parser/internal/classish_statement.rs index 2a27f943..1805667e 100644 --- a/src/parser/internal/classish_statement.rs +++ b/src/parser/internal/classish_statement.rs @@ -147,9 +147,8 @@ impl Parser { } 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()), + Scope::Trait(name) | Scope::Class(name, _, _) => state.named(&name), + Scope::AnonymousClass(_) => state.named(&"class@anonymous".into()), ], state); if flags.contains(&PropertyFlag::Readonly) { @@ -158,7 +157,6 @@ impl Parser { } if value.is_some() { - return Err(ParseError::ReadonlyPropertyHasDefaultValue(class_name, var.to_string(), state.current.span)); } } diff --git a/src/parser/internal/functions.rs b/src/parser/internal/functions.rs index 1cc6e766..7e4b9550 100644 --- a/src/parser/internal/functions.rs +++ b/src/parser/internal/functions.rs @@ -199,7 +199,7 @@ impl Parser { let name = self.ident_maybe_reserved(state)?; let has_body = expected_scope!([ - Scope::Class(_, cf) => { + Scope::Class(_, cf, _) => { if !cf.contains(&ClassFlag::Abstract) && flags.contains(&MethodFlag::Abstract) { return Err(ParseError::AbstractModifierOnNonAbstractClassMethod( state.current.span, @@ -220,7 +220,7 @@ impl Parser { true }, - Scope::AnonymousClass => true, + Scope::AnonymousClass(_) => true, ], state); scoped!(state, Scope::Method(name.clone(), flags.clone()), { diff --git a/src/parser/internal/params.rs b/src/parser/internal/params.rs index a639037d..f8d8bade 100644 --- a/src/parser/internal/params.rs +++ b/src/parser/internal/params.rs @@ -29,14 +29,14 @@ impl Parser { // can only have abstract ctor Scope::Interface(_) => 1, // can only have concret ctor - Scope::AnonymousClass => { + Scope::AnonymousClass(_) => { class_name = state.named(&"class@anonymous".into()); 2 } // can have either abstract or concret ctor, // depens on method flag. - Scope::Class(name, _) | Scope::Trait(name) => { + Scope::Class(name, _, _) | Scope::Trait(name) => { if flags.contains(&MethodFlag::Abstract) { 1 } else { diff --git a/src/parser/internal/types.rs b/src/parser/internal/types.rs index 8af34bf3..58aacd83 100644 --- a/src/parser/internal/types.rs +++ b/src/parser/internal/types.rs @@ -37,9 +37,7 @@ impl Parser { } pub(in crate::parser) fn get_type(&self, state: &mut State) -> ParseResult { - let ty = self.maybe_nullable(state, &|state| { - self.maybe_static(state, &|state| self.get_simple_type(state)) - })?; + let ty = self.maybe_nullable(state, &|state| self.get_simple_type(state))?; if ty.nullable() { return Ok(ty); @@ -57,7 +55,7 @@ impl Parser { let mut types = vec![ty]; while !state.is_eof() { - let ty = self.maybe_static(state, &|state| self.get_simple_type(state))?; + let ty = self.get_simple_type(state)?; if ty.standalone() { return Err(ParseError::StandaloneTypeUsedInCombination( ty, @@ -91,7 +89,7 @@ impl Parser { let mut types = vec![ty]; while !state.is_eof() { - let ty = self.maybe_static(state, &|state| self.get_simple_type(state))?; + let ty = self.get_simple_type(state)?; if ty.standalone() { return Err(ParseError::StandaloneTypeUsedInCombination( ty, @@ -122,7 +120,7 @@ impl Parser { return Ok(Some(self.get_type(state)?)); } - let ty = self.maybe_optional_static(state, &|state| self.get_optional_simple_type(state)); + let ty = self.get_optional_simple_type(state)?; match ty { Some(ty) => { @@ -138,7 +136,7 @@ impl Parser { let mut types = vec![ty]; while !state.is_eof() { - let ty = self.maybe_static(state, &|state| self.get_simple_type(state))?; + let ty = self.get_simple_type(state)?; if ty.standalone() { return Err(ParseError::StandaloneTypeUsedInCombination( ty, @@ -172,7 +170,7 @@ impl Parser { let mut types = vec![ty]; while !state.is_eof() { - let ty = self.maybe_static(state, &|state| self.get_simple_type(state))?; + let ty = self.get_simple_type(state)?; if ty.standalone() { return Err(ParseError::StandaloneTypeUsedInCombination( ty, @@ -198,32 +196,44 @@ impl Parser { } } - fn get_optional_simple_type(&self, state: &mut State) -> Option { + fn get_optional_simple_type(&self, state: &mut State) -> ParseResult> { match state.current.kind.clone() { TokenKind::Array => { state.next(); - Some(Type::Array) + Ok(Some(Type::Array)) } TokenKind::Callable => { state.next(); - Some(Type::Callable) + Ok(Some(Type::Callable)) } TokenKind::Null => { state.next(); - Some(Type::Null) + Ok(Some(Type::Null)) } TokenKind::True => { state.next(); - Some(Type::True) + Ok(Some(Type::True)) } TokenKind::False => { state.next(); - Some(Type::False) + Ok(Some(Type::False)) + } + TokenKind::Static => { + state.next(); + + if !state.has_class_scope { + return Err(ParseError::CannotFindTypeInCurrentScope( + "static".to_owned(), + state.current.span, + )); + } + + Ok(Some(Type::StaticReference)) } TokenKind::Identifier(id) => { state.next(); @@ -231,34 +241,54 @@ impl Parser { let name = &id[..]; let lowered_name = name.to_ascii_lowercase(); match lowered_name.as_slice() { - b"void" => Some(Type::Void), - b"never" => Some(Type::Never), - b"float" => Some(Type::Float), - b"bool" => Some(Type::Boolean), - b"int" => Some(Type::Integer), - b"string" => Some(Type::String), - b"object" => Some(Type::Object), - b"mixed" => Some(Type::Mixed), - b"iterable" => Some(Type::Iterable), - b"null" => Some(Type::Null), - b"true" => Some(Type::True), - b"false" => Some(Type::False), - b"array" => Some(Type::Array), - b"callable" => Some(Type::Callable), - _ => Some(Type::Identifier(id.into())), + b"void" => Ok(Some(Type::Void)), + b"never" => Ok(Some(Type::Never)), + b"float" => Ok(Some(Type::Float)), + b"bool" => Ok(Some(Type::Boolean)), + b"int" => Ok(Some(Type::Integer)), + b"string" => Ok(Some(Type::String)), + b"object" => Ok(Some(Type::Object)), + b"mixed" => Ok(Some(Type::Mixed)), + b"iterable" => Ok(Some(Type::Iterable)), + b"null" => Ok(Some(Type::Null)), + b"true" => Ok(Some(Type::True)), + b"false" => Ok(Some(Type::False)), + b"array" => Ok(Some(Type::Array)), + b"callable" => Ok(Some(Type::Callable)), + b"self" => { + if !state.has_class_scope { + return Err(ParseError::CannotFindTypeInCurrentScope( + "self".to_owned(), + state.current.span, + )); + } + + Ok(Some(Type::SelfReference)) + } + b"parent" => { + if !state.has_class_parent_scope { + return Err(ParseError::CannotFindTypeInCurrentScope( + "parent".to_owned(), + state.current.span, + )); + } + + Ok(Some(Type::ParentReference)) + } + _ => Ok(Some(Type::Identifier(id.into()))), } } TokenKind::QualifiedIdentifier(id) | TokenKind::FullyQualifiedIdentifier(id) => { state.next(); - Some(Type::Identifier(id.into())) + Ok(Some(Type::Identifier(id.into()))) } - _ => None, + _ => Ok(None), } } fn get_simple_type(&self, state: &mut State) -> ParseResult { - self.get_optional_simple_type(state) + self.get_optional_simple_type(state)? .ok_or_else(|| expected_token!(["a type"], state)) } @@ -282,68 +312,4 @@ impl Parser { otherwise(state) } } - - fn maybe_static( - &self, - state: &mut State, - otherwise: &(dyn Fn(&mut State) -> ParseResult), - ) -> ParseResult { - if TokenKind::Static == state.current.kind { - state.next(); - - return Ok(Type::StaticReference); - } - - if let TokenKind::Identifier(id) = &state.current.kind { - let name = &id[..]; - let lowered_name = name.to_ascii_lowercase(); - match lowered_name.as_slice() { - b"self" => { - state.next(); - - return Ok(Type::SelfReference); - } - b"parent" => { - state.next(); - - return Ok(Type::ParentReference); - } - _ => {} - }; - } - - otherwise(state) - } - - fn maybe_optional_static( - &self, - state: &mut State, - otherwise: &(dyn Fn(&mut State) -> Option), - ) -> Option { - if TokenKind::Static == state.current.kind { - state.next(); - - return Some(Type::StaticReference); - } - - if let TokenKind::Identifier(id) = &state.current.kind { - let name = &id[..]; - let lowered_name = name.to_ascii_lowercase(); - match lowered_name.as_slice() { - b"self" => { - state.next(); - - return Some(Type::SelfReference); - } - b"parent" => { - state.next(); - - return Some(Type::ParentReference); - } - _ => {} - }; - } - - otherwise(state) - } } diff --git a/src/parser/state.rs b/src/parser/state.rs index 01f1f24c..1ac9a4a6 100644 --- a/src/parser/state.rs +++ b/src/parser/state.rs @@ -21,10 +21,10 @@ pub enum Scope { BracedNamespace(Option), Interface(ByteString), - Class(ByteString, Vec), + Class(ByteString, Vec, bool), Trait(ByteString), Enum(ByteString, bool), - AnonymousClass, + AnonymousClass(bool), Function(ByteString), Method(ByteString, Vec), @@ -40,6 +40,8 @@ pub struct State { pub iter: IntoIter, pub comments: Vec, pub namespace_type: Option, + pub has_class_scope: bool, + pub has_class_parent_scope: bool, } impl State { @@ -53,6 +55,8 @@ impl State { iter, comments: vec![], namespace_type: None, + has_class_scope: false, + has_class_parent_scope: false, } } @@ -113,10 +117,12 @@ impl State { } self.stack.push_back(scope); + self.update_scope(); } pub fn exit(&mut self) { self.stack.pop_back(); + self.update_scope(); } pub fn skip_comments(&mut self) { @@ -152,4 +158,67 @@ impl State { self.current = self.peek.clone(); self.peek = self.iter.next().unwrap_or_default() } + + fn update_scope(&mut self) { + self.has_class_scope = self.has_class_scope(); + self.has_class_parent_scope = if self.has_class_scope { + self.has_class_parent_scope() + } else { + false + }; + } + + fn has_class_scope(&self) -> bool { + for scope in self.stack.iter().rev() { + match &scope { + Scope::ArrowFunction(s) | Scope::AnonymousFunction(s) => { + // if it's a static closure, don't allow `static` type. + if *s { + return false; + } + } + Scope::BracedNamespace(_) | Scope::Namespace(_) | Scope::Function(_) => { + return false; + } + _ => { + return true; + } + }; + } + + false + } + + fn has_class_parent_scope(&self) -> bool { + for scope in self.stack.iter().rev() { + match &scope { + Scope::ArrowFunction(s) | Scope::AnonymousFunction(s) => { + // static closures don't have a parent + if *s { + return false; + } + } + Scope::BracedNamespace(_) | Scope::Namespace(_) | Scope::Function(_) => { + return false; + } + // we don't know if the trait has a parent at this point + // the only time that we can determine if a trait has a parent + // is when it's used in a class. + Scope::Trait(_) => { + return true; + } + // interfaces and enums don't have a parent. + Scope::Interface(_) | Scope::Enum(_, _) => { + return false; + } + Scope::Class(_, _, has_parent) | Scope::AnonymousClass(has_parent) => { + return *has_parent; + } + // we can't determine this from method, wait until we reach the classish scope. + Scope::Method(_, _) => {} + }; + } + + false + } } diff --git a/tests/0149/ast.txt b/tests/0149/ast.txt deleted file mode 100644 index 9a41f989..00000000 --- a/tests/0149/ast.txt +++ /dev/null @@ -1,49 +0,0 @@ -[ - Namespace { - name: "A\B\C\D\E", - body: [ - Noop, - Function { - name: Identifier { - name: "foo", - }, - params: [ - Param { - name: Variable { - name: "s", - }, - type: Some( - String, - ), - variadic: false, - default: None, - flags: [], - by_ref: false, - }, - ], - body: [ - Expression { - expr: Call { - target: Identifier { - name: "exit", - }, - args: [ - Arg { - name: None, - value: LiteralInteger { - i: 0, - }, - unpack: false, - }, - ], - }, - }, - ], - return_type: Some( - SelfReference, - ), - by_ref: false, - }, - ], - }, -] diff --git a/tests/0149/parser-error.txt b/tests/0149/parser-error.txt new file mode 100644 index 00000000..22f92879 --- /dev/null +++ b/tests/0149/parser-error.txt @@ -0,0 +1 @@ +CannotFindTypeInCurrentScope("self", (5, 31)) -> Parse Error: Cannot find type `self` in this scope on line 5 on column 31 diff --git a/tests/0150/ast.txt b/tests/0150/ast.txt deleted file mode 100644 index 62b12c2c..00000000 --- a/tests/0150/ast.txt +++ /dev/null @@ -1,49 +0,0 @@ -[ - Namespace { - name: "A\B\C\D\E", - body: [ - Noop, - Function { - name: Identifier { - name: "foo", - }, - params: [ - Param { - name: Variable { - name: "s", - }, - type: Some( - String, - ), - variadic: false, - default: None, - flags: [], - by_ref: false, - }, - ], - body: [ - Expression { - expr: Call { - target: Identifier { - name: "exit", - }, - args: [ - Arg { - name: None, - value: LiteralInteger { - i: 0, - }, - unpack: false, - }, - ], - }, - }, - ], - return_type: Some( - StaticReference, - ), - by_ref: false, - }, - ], - }, -] diff --git a/tests/0150/parser-error.txt b/tests/0150/parser-error.txt new file mode 100644 index 00000000..0ccb7536 --- /dev/null +++ b/tests/0150/parser-error.txt @@ -0,0 +1 @@ +CannotFindTypeInCurrentScope("static", (5, 33)) -> Parse Error: Cannot find type `static` in this scope on line 5 on column 33 diff --git a/tests/0151/parser-error.txt b/tests/0151/parser-error.txt new file mode 100644 index 00000000..bfe4f2b9 --- /dev/null +++ b/tests/0151/parser-error.txt @@ -0,0 +1 @@ +CannotFindTypeInCurrentScope("parent", (5, 33)) -> Parse Error: Cannot find type `parent` in this scope on line 5 on column 33 diff --git a/tests/0213/code.php b/tests/0213/code.php new file mode 100644 index 00000000..c562e81e --- /dev/null +++ b/tests/0213/code.php @@ -0,0 +1,6 @@ + Parse Error: Cannot find type `parent` in this scope on line 5 on column 34 diff --git a/tests/0213/tokens.txt b/tests/0213/tokens.txt new file mode 100644 index 00000000..f0436c35 --- /dev/null +++ b/tests/0213/tokens.txt @@ -0,0 +1,101 @@ +[ + Token { + kind: OpenTag( + Full, + ), + span: ( + 1, + 1, + ), + }, + Token { + kind: Interface, + span: ( + 4, + 1, + ), + }, + Token { + kind: Identifier( + "foo", + ), + span: ( + 4, + 11, + ), + }, + Token { + kind: LeftBrace, + span: ( + 4, + 15, + ), + }, + Token { + kind: Public, + span: ( + 5, + 5, + ), + }, + Token { + kind: Function, + span: ( + 5, + 12, + ), + }, + Token { + kind: Identifier( + "bar", + ), + span: ( + 5, + 21, + ), + }, + Token { + kind: LeftParen, + span: ( + 5, + 24, + ), + }, + Token { + kind: RightParen, + span: ( + 5, + 25, + ), + }, + Token { + kind: Colon, + span: ( + 5, + 26, + ), + }, + Token { + kind: Identifier( + "parent", + ), + span: ( + 5, + 28, + ), + }, + Token { + kind: SemiColon, + span: ( + 5, + 34, + ), + }, + Token { + kind: RightBrace, + span: ( + 6, + 1, + ), + }, +] diff --git a/tests/0214/code.php b/tests/0214/code.php new file mode 100644 index 00000000..4d663a33 --- /dev/null +++ b/tests/0214/code.php @@ -0,0 +1,7 @@ + Parse Error: Cannot find type `parent` in this scope on line 6 on column 34 diff --git a/tests/0214/tokens.txt b/tests/0214/tokens.txt new file mode 100644 index 00000000..1209cf39 --- /dev/null +++ b/tests/0214/tokens.txt @@ -0,0 +1,147 @@ +[ + Token { + kind: OpenTag( + Full, + ), + span: ( + 1, + 1, + ), + }, + Token { + kind: Interface, + span: ( + 3, + 1, + ), + }, + Token { + kind: Identifier( + "s", + ), + span: ( + 3, + 11, + ), + }, + Token { + kind: LeftBrace, + span: ( + 3, + 13, + ), + }, + Token { + kind: RightBrace, + span: ( + 3, + 14, + ), + }, + Token { + kind: Interface, + span: ( + 5, + 1, + ), + }, + Token { + kind: Identifier( + "foo", + ), + span: ( + 5, + 11, + ), + }, + Token { + kind: Extends, + span: ( + 5, + 15, + ), + }, + Token { + kind: Identifier( + "s", + ), + span: ( + 5, + 23, + ), + }, + Token { + kind: LeftBrace, + span: ( + 5, + 25, + ), + }, + Token { + kind: Public, + span: ( + 6, + 5, + ), + }, + Token { + kind: Function, + span: ( + 6, + 12, + ), + }, + Token { + kind: Identifier( + "bar", + ), + span: ( + 6, + 21, + ), + }, + Token { + kind: LeftParen, + span: ( + 6, + 24, + ), + }, + Token { + kind: RightParen, + span: ( + 6, + 25, + ), + }, + Token { + kind: Colon, + span: ( + 6, + 26, + ), + }, + Token { + kind: Identifier( + "parent", + ), + span: ( + 6, + 28, + ), + }, + Token { + kind: SemiColon, + span: ( + 6, + 34, + ), + }, + Token { + kind: RightBrace, + span: ( + 7, + 1, + ), + }, +] diff --git a/tests/0151/ast.txt b/tests/0215/ast.txt similarity index 59% rename from tests/0151/ast.txt rename to tests/0215/ast.txt index 01d50219..cb3547fc 100644 --- a/tests/0151/ast.txt +++ b/tests/0215/ast.txt @@ -1,26 +1,14 @@ [ - Namespace { - name: "A\B\C\D\E", + Trait { + name: Identifier { + name: "foo", + }, body: [ - Noop, - Function { + Method { name: Identifier { - name: "foo", + name: "bar", }, - params: [ - Param { - name: Variable { - name: "s", - }, - type: Some( - String, - ), - variadic: false, - default: None, - flags: [], - by_ref: false, - }, - ], + params: [], body: [ Expression { expr: Call { @@ -31,7 +19,7 @@ Arg { name: None, value: LiteralInteger { - i: 0, + i: 1, }, unpack: false, }, @@ -39,6 +27,9 @@ }, }, ], + flags: [ + Public, + ], return_type: Some( ParentReference, ), diff --git a/tests/0215/code.php b/tests/0215/code.php new file mode 100644 index 00000000..d3d0c80d --- /dev/null +++ b/tests/0215/code.php @@ -0,0 +1,10 @@ + Parse Error: Cannot find type `parent` in this scope on line 4 on column 35 diff --git a/tests/0216/tokens.txt b/tests/0216/tokens.txt new file mode 100644 index 00000000..17ec2143 --- /dev/null +++ b/tests/0216/tokens.txt @@ -0,0 +1,147 @@ +[ + Token { + kind: OpenTag( + Full, + ), + span: ( + 1, + 1, + ), + }, + Token { + kind: Class, + span: ( + 3, + 1, + ), + }, + Token { + kind: Identifier( + "foo", + ), + span: ( + 3, + 7, + ), + }, + Token { + kind: LeftBrace, + span: ( + 3, + 11, + ), + }, + Token { + kind: Public, + span: ( + 4, + 5, + ), + }, + Token { + kind: Function, + span: ( + 4, + 12, + ), + }, + Token { + kind: Identifier( + "bar", + ), + span: ( + 4, + 21, + ), + }, + Token { + kind: LeftParen, + span: ( + 4, + 24, + ), + }, + Token { + kind: RightParen, + span: ( + 4, + 25, + ), + }, + Token { + kind: Colon, + span: ( + 4, + 26, + ), + }, + Token { + kind: Identifier( + "parent", + ), + span: ( + 4, + 28, + ), + }, + Token { + kind: LeftBrace, + span: ( + 4, + 35, + ), + }, + Token { + kind: Identifier( + "exit", + ), + span: ( + 5, + 9, + ), + }, + Token { + kind: LeftParen, + span: ( + 5, + 13, + ), + }, + Token { + kind: LiteralInteger( + 1, + ), + span: ( + 5, + 14, + ), + }, + Token { + kind: RightParen, + span: ( + 5, + 15, + ), + }, + Token { + kind: SemiColon, + span: ( + 5, + 16, + ), + }, + Token { + kind: RightBrace, + span: ( + 6, + 5, + ), + }, + Token { + kind: RightBrace, + span: ( + 7, + 1, + ), + }, +] diff --git a/tests/0217/ast.txt b/tests/0217/ast.txt new file mode 100644 index 00000000..6abe17fd --- /dev/null +++ b/tests/0217/ast.txt @@ -0,0 +1,50 @@ +[ + Class { + name: Identifier { + name: "s", + }, + extends: None, + implements: [], + body: [], + flags: [], + }, + Class { + name: Identifier { + name: "foo", + }, + extends: Some( + Identifier { + name: "s", + }, + ), + implements: [], + body: [ + Method { + name: Identifier { + name: "bar", + }, + params: [], + body: [ + Return { + value: Some( + New { + target: Identifier { + name: "s", + }, + args: [], + }, + ), + }, + ], + flags: [ + Public, + ], + return_type: Some( + ParentReference, + ), + by_ref: false, + }, + ], + flags: [], + }, +] diff --git a/tests/0217/code.php b/tests/0217/code.php new file mode 100644 index 00000000..75231022 --- /dev/null +++ b/tests/0217/code.php @@ -0,0 +1,9 @@ + Parse Error: Cannot find type `parent` in this scope on line 4 on column 35 diff --git a/tests/0218/tokens.txt b/tests/0218/tokens.txt new file mode 100644 index 00000000..73ed1a5c --- /dev/null +++ b/tests/0218/tokens.txt @@ -0,0 +1,168 @@ +[ + Token { + kind: OpenTag( + Full, + ), + span: ( + 1, + 1, + ), + }, + Token { + kind: Variable( + "e", + ), + span: ( + 3, + 1, + ), + }, + Token { + kind: Equals, + span: ( + 3, + 4, + ), + }, + Token { + kind: New, + span: ( + 3, + 6, + ), + }, + Token { + kind: Class, + span: ( + 3, + 10, + ), + }, + Token { + kind: LeftBrace, + span: ( + 3, + 16, + ), + }, + Token { + kind: Public, + span: ( + 4, + 5, + ), + }, + Token { + kind: Function, + span: ( + 4, + 12, + ), + }, + Token { + kind: Identifier( + "bar", + ), + span: ( + 4, + 21, + ), + }, + Token { + kind: LeftParen, + span: ( + 4, + 24, + ), + }, + Token { + kind: RightParen, + span: ( + 4, + 25, + ), + }, + Token { + kind: Colon, + span: ( + 4, + 26, + ), + }, + Token { + kind: Identifier( + "parent", + ), + span: ( + 4, + 28, + ), + }, + Token { + kind: LeftBrace, + span: ( + 4, + 35, + ), + }, + Token { + kind: Identifier( + "exit", + ), + span: ( + 5, + 9, + ), + }, + Token { + kind: LeftParen, + span: ( + 5, + 13, + ), + }, + Token { + kind: LiteralInteger( + 1, + ), + span: ( + 5, + 14, + ), + }, + Token { + kind: RightParen, + span: ( + 5, + 15, + ), + }, + Token { + kind: SemiColon, + span: ( + 5, + 16, + ), + }, + Token { + kind: RightBrace, + span: ( + 6, + 5, + ), + }, + Token { + kind: RightBrace, + span: ( + 7, + 1, + ), + }, + Token { + kind: SemiColon, + span: ( + 7, + 2, + ), + }, +] diff --git a/tests/0219/code.php b/tests/0219/code.php new file mode 100644 index 00000000..1e29e2ee --- /dev/null +++ b/tests/0219/code.php @@ -0,0 +1,7 @@ + Parse Error: Cannot find type `parent` in this scope on line 4 on column 35 diff --git a/tests/0219/tokens.txt b/tests/0219/tokens.txt new file mode 100644 index 00000000..481627fd --- /dev/null +++ b/tests/0219/tokens.txt @@ -0,0 +1,147 @@ +[ + Token { + kind: OpenTag( + Full, + ), + span: ( + 1, + 1, + ), + }, + Token { + kind: Enum, + span: ( + 3, + 1, + ), + }, + Token { + kind: Identifier( + "foo", + ), + span: ( + 3, + 6, + ), + }, + Token { + kind: LeftBrace, + span: ( + 3, + 10, + ), + }, + Token { + kind: Public, + span: ( + 4, + 5, + ), + }, + Token { + kind: Function, + span: ( + 4, + 12, + ), + }, + Token { + kind: Identifier( + "bar", + ), + span: ( + 4, + 21, + ), + }, + Token { + kind: LeftParen, + span: ( + 4, + 24, + ), + }, + Token { + kind: RightParen, + span: ( + 4, + 25, + ), + }, + Token { + kind: Colon, + span: ( + 4, + 26, + ), + }, + Token { + kind: Identifier( + "parent", + ), + span: ( + 4, + 28, + ), + }, + Token { + kind: LeftBrace, + span: ( + 4, + 35, + ), + }, + Token { + kind: Identifier( + "exit", + ), + span: ( + 5, + 9, + ), + }, + Token { + kind: LeftParen, + span: ( + 5, + 13, + ), + }, + Token { + kind: LiteralInteger( + 1, + ), + span: ( + 5, + 14, + ), + }, + Token { + kind: RightParen, + span: ( + 5, + 15, + ), + }, + Token { + kind: SemiColon, + span: ( + 5, + 16, + ), + }, + Token { + kind: RightBrace, + span: ( + 6, + 5, + ), + }, + Token { + kind: RightBrace, + span: ( + 7, + 1, + ), + }, +] diff --git a/tests/0220/ast.txt b/tests/0220/ast.txt new file mode 100644 index 00000000..dd5a826f --- /dev/null +++ b/tests/0220/ast.txt @@ -0,0 +1,58 @@ +[ + Class { + name: Identifier { + name: "bar", + }, + extends: None, + implements: [], + body: [], + flags: [], + }, + Noop, + Expression { + expr: Infix { + lhs: Variable { + name: "e", + }, + op: Assign, + rhs: New { + target: AnonymousClass { + extends: Some( + Identifier { + name: "bar", + }, + ), + implements: [], + body: [ + Method { + name: Identifier { + name: "bar", + }, + params: [], + body: [ + Return { + value: Some( + New { + target: Identifier { + name: "bar", + }, + args: [], + }, + ), + }, + ], + flags: [ + Public, + ], + return_type: Some( + ParentReference, + ), + by_ref: false, + }, + ], + }, + args: [], + }, + }, + }, +] diff --git a/tests/0220/code.php b/tests/0220/code.php new file mode 100644 index 00000000..a0ab3223 --- /dev/null +++ b/tests/0220/code.php @@ -0,0 +1,10 @@ +