[red-knot] More Type constructors (#14227)

This commit is contained in:
Alex Waygood 2024-11-09 16:57:11 +00:00 committed by GitHub
parent c9b84e2a85
commit e598240f04
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 84 additions and 115 deletions

View file

@ -478,6 +478,18 @@ impl<'db> Type<'db> {
Self::SubclassOf(SubclassOfType { class })
}
pub fn string_literal(db: &'db dyn Db, string: &str) -> Self {
Self::StringLiteral(StringLiteralType::new(db, string))
}
pub fn bytes_literal(db: &'db dyn Db, bytes: &[u8]) -> Self {
Self::BytesLiteral(BytesLiteralType::new(db, bytes))
}
pub fn tuple(db: &'db dyn Db, elements: &[Type<'db>]) -> Self {
Self::Tuple(TupleType::new(db, elements))
}
#[must_use]
pub fn negate(&self, db: &'db dyn Db) -> Type<'db> {
IntersectionBuilder::new(db).add_negative(*self).build()
@ -1458,7 +1470,7 @@ impl<'db> Type<'db> {
Type::IntLiteral(_) | Type::BooleanLiteral(_) => self.repr(db),
Type::StringLiteral(_) | Type::LiteralString => *self,
Type::KnownInstance(known_instance) => {
Type::StringLiteral(StringLiteralType::new(db, known_instance.repr(db)))
Type::string_literal(db, known_instance.repr(db))
}
// TODO: handle more complex types
_ => KnownClass::Str.to_instance(db),
@ -1470,17 +1482,15 @@ impl<'db> Type<'db> {
#[must_use]
pub fn repr(&self, db: &'db dyn Db) -> Type<'db> {
match self {
Type::IntLiteral(number) => Type::StringLiteral(StringLiteralType::new(db, {
number.to_string().into_boxed_str()
})),
Type::BooleanLiteral(true) => Type::StringLiteral(StringLiteralType::new(db, "True")),
Type::BooleanLiteral(false) => Type::StringLiteral(StringLiteralType::new(db, "False")),
Type::StringLiteral(literal) => Type::StringLiteral(StringLiteralType::new(db, {
format!("'{}'", literal.value(db).escape_default()).into_boxed_str()
})),
Type::IntLiteral(number) => Type::string_literal(db, &number.to_string()),
Type::BooleanLiteral(true) => Type::string_literal(db, "True"),
Type::BooleanLiteral(false) => Type::string_literal(db, "False"),
Type::StringLiteral(literal) => {
Type::string_literal(db, &format!("'{}'", literal.value(db).escape_default()))
}
Type::LiteralString => Type::LiteralString,
Type::KnownInstance(known_instance) => {
Type::StringLiteral(StringLiteralType::new(db, known_instance.repr(db)))
Type::string_literal(db, known_instance.repr(db))
}
// TODO: handle more complex types
_ => KnownClass::Str.to_instance(db),
@ -1738,42 +1748,28 @@ impl<'db> KnownInstanceType<'db> {
}
fn member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
match (self, name) {
(Self::TypeVar(typevar), "__name__") => Symbol::Type(
Type::StringLiteral(StringLiteralType::new(db, typevar.name(db).as_str())),
Boundness::Bound,
),
(Self::TypeVar(typevar), "__bound__") => Symbol::Type(
typevar
.upper_bound(db)
let ty = match (self, name) {
(Self::TypeVar(typevar), "__name__") => Type::string_literal(db, typevar.name(db)),
(Self::TypeVar(typevar), "__bound__") => typevar
.upper_bound(db)
.map(|ty| ty.to_meta_type(db))
.unwrap_or_else(|| KnownClass::NoneType.to_instance(db)),
(Self::TypeVar(typevar), "__constraints__") => {
let tuple_elements: Vec<Type<'db>> = typevar
.constraints(db)
.unwrap_or_default()
.iter()
.map(|ty| ty.to_meta_type(db))
.unwrap_or(KnownClass::NoneType.to_instance(db)),
Boundness::Bound,
),
(Self::TypeVar(typevar), "__constraints__") => Symbol::Type(
Type::Tuple(TupleType::new(
db,
typevar
.constraints(db)
.map(|constraints| {
constraints
.iter()
.map(|ty| ty.to_meta_type(db))
.collect::<Box<_>>()
})
.unwrap_or_else(|| std::iter::empty().collect::<Box<_>>()),
)),
Boundness::Bound,
),
(Self::TypeVar(typevar), "__default__") => Symbol::Type(
typevar
.default_ty(db)
.map(|ty| ty.to_meta_type(db))
.unwrap_or_else(|| KnownClass::NoDefaultType.to_instance(db)),
Boundness::Bound,
),
_ => self.instance_fallback(db).member(db, name),
}
.collect();
Type::tuple(db, &tuple_elements)
}
(Self::TypeVar(typevar), "__default__") => typevar
.default_ty(db)
.map(|ty| ty.to_meta_type(db))
.unwrap_or_else(|| KnownClass::NoDefaultType.to_instance(db)),
_ => return self.instance_fallback(db).member(db, name),
};
ty.into()
}
}
@ -2544,11 +2540,8 @@ impl<'db> Class<'db> {
/// The member resolves to a member on the class itself or any of its proper superclasses.
pub(crate) fn class_member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
if name == "__mro__" {
let tuple_elements: Box<_> = self.iter_mro(db).map(Type::from).collect();
return Symbol::Type(
Type::Tuple(TupleType::new(db, tuple_elements)),
Boundness::Bound,
);
let tuple_elements: Vec<Type<'db>> = self.iter_mro(db).map(Type::from).collect();
return Type::tuple(db, &tuple_elements).into();
}
if name == "__class__" {
@ -2880,10 +2873,10 @@ mod tests {
Ty::Any => Type::Any,
Ty::Todo => Type::Todo,
Ty::IntLiteral(n) => Type::IntLiteral(n),
Ty::StringLiteral(s) => Type::StringLiteral(StringLiteralType::new(db, s)),
Ty::StringLiteral(s) => Type::string_literal(db, s),
Ty::BooleanLiteral(b) => Type::BooleanLiteral(b),
Ty::LiteralString => Type::LiteralString,
Ty::BytesLiteral(s) => Type::BytesLiteral(BytesLiteralType::new(db, s.as_bytes())),
Ty::BytesLiteral(s) => Type::bytes_literal(db, s.as_bytes()),
Ty::BuiltinInstance(s) => builtins_symbol(db, s).expect_type().to_instance(db),
Ty::TypingInstance(s) => typing_symbol(db, s).expect_type().to_instance(db),
Ty::TypingLiteral => Type::KnownInstance(KnownInstanceType::Literal),
@ -2903,8 +2896,8 @@ mod tests {
builder.build()
}
Ty::Tuple(tys) => {
let elements: Box<_> = tys.into_iter().map(|ty| ty.into_type(db)).collect();
Type::Tuple(TupleType::new(db, elements))
let elements: Vec<Type> = tys.into_iter().map(|ty| ty.into_type(db)).collect();
Type::tuple(db, &elements)
}
}
}

View file

@ -383,7 +383,7 @@ mod tests {
use crate::program::{Program, SearchPathSettings};
use crate::python_version::PythonVersion;
use crate::stdlib::typing_symbol;
use crate::types::{global_symbol, KnownClass, StringLiteralType, UnionBuilder};
use crate::types::{global_symbol, KnownClass, UnionBuilder};
use crate::ProgramSettings;
use ruff_db::files::system_path_to_file;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
@ -775,7 +775,7 @@ mod tests {
.build();
assert_eq!(ty, s);
let literal = Type::StringLiteral(StringLiteralType::new(&db, "a"));
let literal = Type::string_literal(&db, "a");
let expected = IntersectionBuilder::new(&db)
.add_positive(s)
.add_negative(literal)
@ -878,7 +878,7 @@ mod tests {
let ty = IntersectionBuilder::new(&db)
.add_positive(s)
.add_negative(Type::StringLiteral(StringLiteralType::new(&db, "a")))
.add_negative(Type::string_literal(&db, "a"))
.add_negative(t)
.build();
assert_eq!(ty, Type::Never);
@ -912,7 +912,7 @@ mod tests {
let db = setup_db();
let t_p = KnownClass::Int.to_instance(&db);
let t_n = Type::StringLiteral(StringLiteralType::new(&db, "t_n"));
let t_n = Type::string_literal(&db, "t_n");
let ty = IntersectionBuilder::new(&db)
.add_positive(t_p)

View file

@ -337,9 +337,7 @@ mod tests {
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
use crate::db::tests::TestDb;
use crate::types::{
global_symbol, BytesLiteralType, SliceLiteralType, StringLiteralType, Type, UnionType,
};
use crate::types::{global_symbol, SliceLiteralType, Type, UnionType};
use crate::{Program, ProgramSettings, PythonVersion, SearchPathSettings};
fn setup_db() -> TestDb {
@ -385,12 +383,12 @@ mod tests {
Type::Unknown,
Type::IntLiteral(-1),
global_symbol(&db, mod_file, "A").expect_type(),
Type::StringLiteral(StringLiteralType::new(&db, "A")),
Type::BytesLiteral(BytesLiteralType::new(&db, [0u8].as_slice())),
Type::BytesLiteral(BytesLiteralType::new(&db, [7u8].as_slice())),
Type::string_literal(&db, "A"),
Type::bytes_literal(&db, &[0u8]),
Type::bytes_literal(&db, &[7u8]),
Type::IntLiteral(0),
Type::IntLiteral(1),
Type::StringLiteral(StringLiteralType::new(&db, "B")),
Type::string_literal(&db, "B"),
global_symbol(&db, mod_file, "foo").expect_type(),
global_symbol(&db, mod_file, "bar").expect_type(),
global_symbol(&db, mod_file, "B").expect_type(),

View file

@ -56,11 +56,10 @@ use crate::types::mro::MroErrorKind;
use crate::types::unpacker::{UnpackResult, Unpacker};
use crate::types::{
bindings_ty, builtins_symbol, declarations_ty, global_symbol, symbol, typing_extensions_symbol,
Boundness, BytesLiteralType, Class, ClassLiteralType, FunctionType, InstanceType,
IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass, KnownFunction,
KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, StringLiteralType,
Symbol, Truthiness, TupleType, Type, TypeArrayDisplay, TypeVarBoundOrConstraints,
TypeVarInstance, UnionBuilder, UnionType,
Boundness, Class, ClassLiteralType, FunctionType, InstanceType, IntersectionBuilder,
IntersectionType, IterationOutcome, KnownClass, KnownFunction, KnownInstanceType,
MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, Symbol, Truthiness, TupleType, Type,
TypeArrayDisplay, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType,
};
use crate::unpack::Unpack;
use crate::util::subscript::{PyIndex, PySlice};
@ -2202,7 +2201,7 @@ impl<'db> TypeInferenceBuilder<'db> {
fn infer_string_literal_expression(&mut self, literal: &ast::ExprStringLiteral) -> Type<'db> {
if literal.value.len() <= Self::MAX_STRING_LITERAL_SIZE {
Type::StringLiteral(StringLiteralType::new(self.db, literal.value.to_str()))
Type::string_literal(self.db, literal.value.to_str())
} else {
Type::LiteralString
}
@ -2210,10 +2209,8 @@ impl<'db> TypeInferenceBuilder<'db> {
fn infer_bytes_literal_expression(&mut self, literal: &ast::ExprBytesLiteral) -> Type<'db> {
// TODO: ignoring r/R prefixes for now, should normalize bytes values
Type::BytesLiteral(BytesLiteralType::new(
self.db,
literal.value.bytes().collect::<Box<[u8]>>(),
))
let bytes: Vec<u8> = literal.value.bytes().collect();
Type::bytes_literal(self.db, &bytes)
}
fn infer_fstring_expression(&mut self, fstring: &ast::ExprFString) -> Type<'db> {
@ -2280,12 +2277,10 @@ impl<'db> TypeInferenceBuilder<'db> {
parenthesized: _,
} = tuple;
let element_types = elts
.iter()
.map(|elt| self.infer_expression(elt))
.collect::<Vec<_>>();
let element_types: Vec<Type<'db>> =
elts.iter().map(|elt| self.infer_expression(elt)).collect();
Type::Tuple(TupleType::new(self.db, element_types.into_boxed_slice()))
Type::tuple(self.db, &element_types)
}
fn infer_list_expression(&mut self, list: &ast::ExprList) -> Type<'db> {
@ -2987,21 +2982,15 @@ impl<'db> TypeInferenceBuilder<'db> {
}
(Type::BytesLiteral(lhs), Type::BytesLiteral(rhs), ast::Operator::Add) => {
Some(Type::BytesLiteral(BytesLiteralType::new(
self.db,
[lhs.value(self.db).as_ref(), rhs.value(self.db).as_ref()]
.concat()
.into_boxed_slice(),
)))
let bytes = [&**lhs.value(self.db), &**rhs.value(self.db)].concat();
Some(Type::bytes_literal(self.db, &bytes))
}
(Type::StringLiteral(lhs), Type::StringLiteral(rhs), ast::Operator::Add) => {
let lhs_value = lhs.value(self.db).to_string();
let rhs_value = rhs.value(self.db).as_ref();
let ty = if lhs_value.len() + rhs_value.len() <= Self::MAX_STRING_LITERAL_SIZE {
Type::StringLiteral(StringLiteralType::new(self.db, {
(lhs_value + rhs_value).into_boxed_str()
}))
Type::string_literal(self.db, &(lhs_value + rhs_value))
} else {
Type::LiteralString
};
@ -3017,16 +3006,13 @@ impl<'db> TypeInferenceBuilder<'db> {
(Type::StringLiteral(s), Type::IntLiteral(n), ast::Operator::Mult)
| (Type::IntLiteral(n), Type::StringLiteral(s), ast::Operator::Mult) => {
let ty = if n < 1 {
Type::StringLiteral(StringLiteralType::new(self.db, ""))
Type::string_literal(self.db, "")
} else if let Ok(n) = usize::try_from(n) {
if n.checked_mul(s.value(self.db).len())
.is_some_and(|new_length| new_length <= Self::MAX_STRING_LITERAL_SIZE)
{
let new_literal = s.value(self.db).repeat(n);
Type::StringLiteral(StringLiteralType::new(
self.db,
new_literal.into_boxed_str(),
))
Type::string_literal(self.db, &new_literal)
} else {
Type::LiteralString
}
@ -3039,7 +3025,7 @@ impl<'db> TypeInferenceBuilder<'db> {
(Type::LiteralString, Type::IntLiteral(n), ast::Operator::Mult)
| (Type::IntLiteral(n), Type::LiteralString, ast::Operator::Mult) => {
let ty = if n < 1 {
Type::StringLiteral(StringLiteralType::new(self.db, ""))
Type::string_literal(self.db, "")
} else {
Type::LiteralString
};
@ -3752,7 +3738,7 @@ impl<'db> TypeInferenceBuilder<'db> {
if let Ok(new_elements) = elements.py_slice(start, stop, step) {
let new_elements: Vec<_> = new_elements.copied().collect();
Type::Tuple(TupleType::new(self.db, new_elements.into_boxed_slice()))
Type::tuple(self.db, &new_elements)
} else {
self.diagnostics.add_slice_step_size_zero(value_node.into());
Type::Unknown
@ -3766,12 +3752,7 @@ impl<'db> TypeInferenceBuilder<'db> {
literal_value
.chars()
.py_index(i32::try_from(int).expect("checked in branch arm"))
.map(|ch| {
Type::StringLiteral(StringLiteralType::new(
self.db,
ch.to_string().into_boxed_str(),
))
})
.map(|ch| Type::string_literal(self.db, &ch.to_string()))
.unwrap_or_else(|_| {
self.diagnostics.add_index_out_of_bounds(
"string",
@ -3791,7 +3772,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let chars: Vec<_> = literal_value.chars().collect();
let result = if let Ok(new_chars) = chars.py_slice(start, stop, step) {
let literal: String = new_chars.collect();
Type::StringLiteral(StringLiteralType::new(self.db, literal.into_boxed_str()))
Type::string_literal(self.db, &literal)
} else {
self.diagnostics.add_slice_step_size_zero(value_node.into());
Type::Unknown
@ -3806,9 +3787,7 @@ impl<'db> TypeInferenceBuilder<'db> {
literal_value
.iter()
.py_index(i32::try_from(int).expect("checked in branch arm"))
.map(|byte| {
Type::BytesLiteral(BytesLiteralType::new(self.db, [*byte].as_slice()))
})
.map(|byte| Type::bytes_literal(self.db, &[*byte]))
.unwrap_or_else(|_| {
self.diagnostics.add_index_out_of_bounds(
"bytes literal",
@ -3827,7 +3806,7 @@ impl<'db> TypeInferenceBuilder<'db> {
if let Ok(new_bytes) = literal_value.py_slice(start, stop, step) {
let new_bytes: Vec<u8> = new_bytes.copied().collect();
Type::BytesLiteral(BytesLiteralType::new(self.db, new_bytes.into_boxed_slice()))
Type::bytes_literal(self.db, &new_bytes)
} else {
self.diagnostics.add_slice_step_size_zero(value_node.into());
Type::Unknown
@ -4262,7 +4241,7 @@ impl<'db> TypeInferenceBuilder<'db> {
if return_todo {
Type::Todo
} else {
Type::Tuple(TupleType::new(self.db, element_types.into_boxed_slice()))
Type::tuple(self.db, &element_types)
}
}
single_element => {
@ -4270,7 +4249,7 @@ impl<'db> TypeInferenceBuilder<'db> {
if element_could_alter_type_of_whole_tuple(single_element, single_element_ty) {
Type::Todo
} else {
Type::Tuple(TupleType::new(self.db, Box::from([single_element_ty])))
Type::tuple(self.db, &[single_element_ty])
}
}
}
@ -4541,7 +4520,7 @@ impl StringPartsCollector {
if self.expression {
KnownClass::Str.to_instance(db)
} else if let Some(concatenated) = self.concatenated {
Type::StringLiteral(StringLiteralType::new(db, concatenated.into_boxed_str()))
Type::string_literal(db, &concatenated)
} else {
Type::LiteralString
}

View file

@ -6,7 +6,7 @@ use rustc_hash::FxHashMap;
use crate::semantic_index::ast_ids::{HasScopedAstId, ScopedExpressionId};
use crate::semantic_index::symbol::ScopeId;
use crate::types::{TupleType, Type, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder};
use crate::types::{Type, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder};
use crate::Db;
/// Unpacks the value expression type to their respective targets.
@ -93,11 +93,10 @@ impl<'db> Unpacker<'db> {
// further and deconstruct to an array of `StringLiteral` with each
// individual character, instead of just an array of `LiteralString`, but
// there would be a cost and it's not clear that it's worth it.
let value_ty = Type::Tuple(TupleType::new(
let value_ty = Type::tuple(
self.db,
vec![Type::LiteralString; string_literal_ty.len(self.db)]
.into_boxed_slice(),
));
&vec![Type::LiteralString; string_literal_ty.len(self.db)],
);
self.unpack(target, value_ty, scope);
}
_ => {