Skip to content

Commit

Permalink
Inference+Parser+Type: Infer type of new expression on literal strings
Browse files Browse the repository at this point in the history
  • Loading branch information
ryangjchandler committed Jan 10, 2025
1 parent 132e72b commit 3616c5d
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 7 deletions.
10 changes: 10 additions & 0 deletions crates/bytestring/src/bytestr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ impl ByteStr {
ByteStr::new(&self.0[start..])
}

pub fn strip_string_quotes(&self) -> &ByteStr {
if self.0[0] == b'"' && self.0[self.0.len() - 1] == b'"' {
ByteStr::new(&self.0[1..self.0.len() - 1])
} else if self.0[0] == b'\'' && self.0[self.0.len() - 1] == b'\'' {
ByteStr::new(&self.0[1..self.0.len() - 1])
} else {
self
}
}

pub fn coagulate(&self, others: &[&ByteStr], with: u8) -> ByteString {
let mut bytes = self.0.to_vec();

Expand Down
26 changes: 24 additions & 2 deletions crates/inference/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ impl Scope {
}

impl<'a> TypeMapGenerator<'a> {
fn is_newable_string(&self, value: &ByteStr) -> bool {
self.index.get_class(value).is_some()
}

fn is_callable_string(&self, name: &ByteStr) -> bool {
let name: &ByteStr = name[1..name.len() - 1].into();

Expand Down Expand Up @@ -278,7 +282,13 @@ impl<'a> Visitor for TypeMapGenerator<'a> {
match node.kind {
LiteralKind::Integer => Type::Integer,
LiteralKind::Float => Type::Float,
LiteralKind::String => Type::String,
LiteralKind::String => Type::LiteralString(
node.token
.symbol
.as_bytestr()
.strip_string_quotes()
.to_bytestring(),
),
LiteralKind::Missing => Type::Missing,
},
)
Expand Down Expand Up @@ -349,7 +359,19 @@ impl<'a> Visitor for TypeMapGenerator<'a> {
_ if name.is_resolved() => Type::Named(name.to_resolved().clone()),
_ => Type::Mixed,
},
_ => Type::Mixed,
_ => match self.map.resolve(node.target.id) {
Type::LiteralString(value) if self.is_newable_string(value.as_ref()) => {
Type::Named(ResolvedName {
resolved: value.clone(),
original: value.clone(),
})
}
_ => {
dbg!(self.map.resolve(node.target.id), &node.target);

Type::Object
}
},
},
);
}
Expand Down
45 changes: 40 additions & 5 deletions crates/inference/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@ mod tests {

#[test]
fn it_infers_string_literals() {
assert_eq!(infer("'Hello, world!'"), Type::String);
assert_eq!(infer("\"Hello, world!\""), Type::String);
assert_eq!(
infer("'Hello, world!'"),
Type::LiteralString(b"Hello, world!".into())
);
assert_eq!(
infer("\"Hello, world!\""),
Type::LiteralString(b"Hello, world!".into())
);
}

#[test]
Expand Down Expand Up @@ -126,15 +132,27 @@ mod tests {
fn it_infers_type_of_keyed_array() {
assert_eq!(
infer(r#"$a = ['a' => 1, 'b' => 2]"#),
Type::TypedArray(Box::new(Type::String), Box::new(Type::Integer),)
Type::TypedArray(
Box::new(Type::Union(vec![
Type::LiteralString(b"a".into()),
Type::LiteralString(b"b".into())
])),
Box::new(Type::Integer)
)
)
}

#[test]
fn it_infers_type_of_mixed_keyed_array() {
assert_eq!(
infer(r#"$a = ['a' => 1, 2]"#),
Type::TypedArray(Box::new(Type::array_key_types()), Box::new(Type::Integer),),
Type::TypedArray(
Box::new(Type::Union(vec![
Type::LiteralString(b"a".into()),
Type::Integer
])),
Box::new(Type::Integer)
),
)
}

Expand All @@ -151,7 +169,24 @@ mod tests {

match inferred {
Type::Named(name) => assert_eq!(name.resolved, b"A"),
_ => panic!("Expected a named type."),
_ => panic!("Expected a named type 'A'."),
}
}

#[test]
fn it_infers_type_of_new_expression_on_class_string_literal() {
let inferred = infer(
r#"
class A {}
$a = 'A';
new $a()"#,
);

assert!(inferred.is_named());

match inferred {
Type::Named(name) => assert_eq!(name.resolved, b"A"),
_ => panic!("Expected a named type 'A'."),
}
}

Expand Down
6 changes: 6 additions & 0 deletions crates/parser/src/internal/data_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,12 @@ impl<'a> Parser<'a> {
b"non-empty-array" if parser.is_in_docblock() => Some(Type::NonEmptyArray),
b"non-empty-list" if parser.is_in_docblock() => Some(Type::NonEmptyList),
b"callable-string" if parser.is_in_docblock() => Some(Type::CallableString),
b"literal-string" if parser.is_in_docblock() => Some(Type::LiteralString(
parser
.current_symbol()
.strip_string_quotes()
.to_bytestring(),
)),
_ => {
let id = parser.id();

Expand Down
11 changes: 11 additions & 0 deletions crates/parser/src/internal/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1537,6 +1537,17 @@ impl<'a> Parser<'a> {
CommentGroup::default(),
)
}),
TokenKind::Variable => {
let variable = self.parse_simple_variable();
let span = variable.span;

Expression::new(
self.id(),
ExpressionKind::Variable(Box::new(Variable::SimpleVariable(variable))),
span,
CommentGroup::default(),
)
}
_ => self.clone_or_new_precedence(),
};

Expand Down
2 changes: 2 additions & 0 deletions crates/type/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub enum Type<N: Debug + Display> {
NonNegativeInteger,
ClassString,
String,
LiteralString(ByteString),
NumericString,
NonEmptyString,
Empty,
Expand Down Expand Up @@ -244,6 +245,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::LiteralString(_) => write!(f, "literal-string"),
Type::CallableString => write!(f, "callable-string"),
Type::NonEmptyList => write!(f, "non-empty-list"),
Type::NonEmptyArray => write!(f, "non-empty-array"),
Expand Down

0 comments on commit 3616c5d

Please sign in to comment.