Skip to content

Commit 52d8847

Browse files
dylwil3carljm
andauthored
[red-knot] Literal[True,False] normalized to builtins.bool (#13178)
The `UnionBuilder` builds `builtins.bool` when handed `Literal[True]` and `Literal[False]`. Caveat: If the builtins module is unfindable somehow, the builder falls back to the union type of these two literals. First task from #12694 --------- Co-authored-by: Carl Meyer <carl@astral.sh>
1 parent d3b6e8f commit 52d8847

1 file changed

Lines changed: 72 additions & 5 deletions

File tree

  • crates/red_knot_python_semantic/src/types

crates/red_knot_python_semantic/src/types/builder.rs

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
use crate::types::{IntersectionType, Type, UnionType};
2929
use crate::{Db, FxOrderSet};
3030

31+
use super::builtins_symbol_ty_by_name;
32+
3133
pub(crate) struct UnionBuilder<'db> {
3234
elements: FxOrderSet<Type<'db>>,
3335
db: &'db dyn Db,
@@ -56,7 +58,24 @@ impl<'db> UnionBuilder<'db> {
5658
self
5759
}
5860

59-
pub(crate) fn build(self) -> Type<'db> {
61+
/// Performs the following normalizations:
62+
/// - Replaces `Literal[True,False]` with `bool`.
63+
/// - TODO For enums `E` with members `X1`,...,`Xn`, replaces
64+
/// `Literal[E.X1,...,E.Xn]` with `E`.
65+
fn simplify(&mut self) {
66+
if self
67+
.elements
68+
.is_superset(&[Type::BooleanLiteral(true), Type::BooleanLiteral(false)].into())
69+
{
70+
let bool_ty = builtins_symbol_ty_by_name(self.db, "bool");
71+
self.elements.remove(&Type::BooleanLiteral(true));
72+
self.elements.remove(&Type::BooleanLiteral(false));
73+
self.elements.insert(bool_ty);
74+
}
75+
}
76+
77+
pub(crate) fn build(mut self) -> Type<'db> {
78+
self.simplify();
6079
match self.elements.len() {
6180
0 => Type::Never,
6281
1 => self.elements[0],
@@ -247,17 +266,38 @@ impl<'db> InnerIntersectionBuilder<'db> {
247266
mod tests {
248267
use super::{IntersectionBuilder, IntersectionType, Type, UnionBuilder, UnionType};
249268
use crate::db::tests::TestDb;
250-
251-
fn setup_db() -> TestDb {
252-
TestDb::new()
253-
}
269+
use crate::program::{Program, SearchPathSettings};
270+
use crate::python_version::PythonVersion;
271+
use crate::types::builtins_symbol_ty_by_name;
272+
use crate::ProgramSettings;
273+
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
254274

255275
impl<'db> UnionType<'db> {
256276
fn elements_vec(self, db: &'db TestDb) -> Vec<Type<'db>> {
257277
self.elements(db).into_iter().collect()
258278
}
259279
}
260280

281+
fn setup_db() -> TestDb {
282+
let db = TestDb::new();
283+
284+
let src_root = SystemPathBuf::from("/src");
285+
db.memory_file_system()
286+
.create_directory_all(&src_root)
287+
.unwrap();
288+
289+
Program::from_settings(
290+
&db,
291+
&ProgramSettings {
292+
target_version: PythonVersion::default(),
293+
search_paths: SearchPathSettings::new(src_root),
294+
},
295+
)
296+
.expect("Valid search path settings");
297+
298+
db
299+
}
300+
261301
#[test]
262302
fn build_union() {
263303
let db = setup_db();
@@ -296,6 +336,33 @@ mod tests {
296336
assert_eq!(ty, t0);
297337
}
298338

339+
#[test]
340+
fn build_union_bool() {
341+
let db = setup_db();
342+
let bool_ty = builtins_symbol_ty_by_name(&db, "bool");
343+
344+
let t0 = Type::BooleanLiteral(true);
345+
let t1 = Type::BooleanLiteral(true);
346+
let t2 = Type::BooleanLiteral(false);
347+
let t3 = Type::IntLiteral(17);
348+
349+
let Type::Union(union) = UnionBuilder::new(&db).add(t0).add(t1).add(t3).build() else {
350+
panic!("expected a union");
351+
};
352+
assert_eq!(union.elements_vec(&db), &[t0, t3]);
353+
let Type::Union(union) = UnionBuilder::new(&db)
354+
.add(t0)
355+
.add(t1)
356+
.add(t2)
357+
.add(t3)
358+
.build()
359+
else {
360+
panic!("expected a union");
361+
};
362+
363+
assert_eq!(union.elements_vec(&db), &[t3, bool_ty]);
364+
}
365+
299366
#[test]
300367
fn build_union_flatten() {
301368
let db = setup_db();

0 commit comments

Comments
 (0)