Skip to content

Commit

Permalink
Lexer+Parser: Add support for conditional callable parameters in docs
Browse files Browse the repository at this point in the history
  • Loading branch information
ryangjchandler committed Dec 19, 2024
1 parent 67643ee commit 71c1972
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 5 deletions.
2 changes: 2 additions & 0 deletions crates/inference/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,9 @@ fn bytestring_type(ty: &Type<Name>) -> Type<ByteString> {
then: Box::new(bytestring_type(then)),
otherwise: Box::new(bytestring_type(otherwise)),
},
Type::Conditional { .. } => todo!(),
Type::This => Type::This,
Type::ValueOf => Type::ValueOf,
Type::Missing => Type::Missing,
}
}
8 changes: 8 additions & 0 deletions crates/lexer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,14 @@ impl<'a> Lexer<'a> {
self.source.span_range(self.source.span()),
)
}
b"value" if &self.source.read(3) == b"-of" => {
self.source.skip(3);

(
self.source.span(),
self.source.span_range(self.source.span()),
)
}
_ => (
self.source.span(),
self.source.span_range(self.source.span()),
Expand Down
47 changes: 42 additions & 5 deletions crates/parser/src/internal/data_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,44 @@ impl<'a> Parser<'a> {
r#type
}

fn parse_docblock_conditional(&mut self, lhs: Type<Name>) -> Type<Name> {
self.skip(TokenKind::PhpDocIs);
self.skip_doc_eol();

let negated = if self.current_kind() == TokenKind::PhpDocNot {
self.next();
true
} else {
false
};

let target_type = self.parse_docblock_type();

self.skip_doc_eol();

self.skip(TokenKind::Question);

self.skip_doc_eol();

let if_type = self.parse_docblock_type();

self.skip_doc_eol();

self.skip(TokenKind::Colon);

self.skip_doc_eol();

let else_type = self.parse_docblock_subparse();

Type::Conditional {
subject: Box::new(lhs),
negated,
target: Box::new(target_type),
then: Box::new(if_type),
otherwise: Box::new(else_type),
}
}

fn parse_docblock_conditional_for_parameter(&mut self) -> Type<Name> {
let parameter = self.current_symbol_as_bytestring();

Expand Down Expand Up @@ -438,13 +476,11 @@ impl<'a> Parser<'a> {
return Type::Missing;
}

if self.current_kind() == TokenKind::Identifier && self.current_symbol() == b"is" {
todo!("parse docblock conditional type");
}

self.skip_doc_eol();

if self.current_kind() == TokenKind::Pipe {
if self.current_kind() == TokenKind::PhpDocIs {
self.parse_docblock_conditional(r#type)
} else if self.current_kind() == TokenKind::Pipe {
self.parse_docblock_subparse_union(r#type)
} else if self.current_kind() == TokenKind::Ampersand {
self.parse_docblock_subparse_intersection(r#type)
Expand Down Expand Up @@ -559,6 +595,7 @@ impl<'a> Parser<'a> {
b"array" => Some(Type::Array),
b"callable" => Some(Type::Callable),
b"array-key" if parser.is_in_docblock() => Some(Type::ArrayKey),
b"value-of" if parser.is_in_docblock() => Some(Type::ValueOf),
_ => {
let id = parser.id();

Expand Down
10 changes: 10 additions & 0 deletions crates/type/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ pub enum Type<N: Debug + Display> {
then: Box<Type<N>>,
otherwise: Box<Type<N>>,
},
Conditional {
subject: Box<Type<N>>,
negated: bool,
target: Box<Type<N>>,
then: Box<Type<N>>,
otherwise: Box<Type<N>>,
},
ValueOf,
This,
Missing,
}
Expand Down Expand Up @@ -122,6 +130,7 @@ impl<N: Debug + Display> Type<N> {
impl<N: Debug + Display> Display for Type<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
Type::ValueOf => write!(f, "value-of"),
Type::Named(inner) => write!(f, "{}", inner),
Type::Generic(inner, templates) => {
write!(
Expand Down Expand Up @@ -202,6 +211,7 @@ impl<N: Debug + Display> Display for Type<N> {
otherwise
)
}
Type::Conditional { .. } => todo!(),
Type::Missing => write!(f, "<missing>"),
}
}
Expand Down

0 comments on commit 71c1972

Please sign in to comment.