Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[red-knot] Migrate is_disjoint_from unit tests to Markdown tests #15580

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,160 @@
If two types can be disjoint, it means that it is known that no possible runtime object could ever
inhabit both types simultaneously.

TODO: Most of our disjointness tests are still Rust tests; they should be moved to this file.
## `Never`

`Never` is disjoint from every type, including itself.

```py
from typing_extensions import Never
from knot_extensions import is_disjoint_from, static_assert

static_assert(is_disjoint_from(Never, Never))
static_assert(is_disjoint_from(Never, None))
static_assert(is_disjoint_from(Never, int))
```

## `None`

```py
from typing_extensions import Literal
from knot_extensions import AlwaysFalsy, AlwaysTruthy, is_disjoint_from, static_assert

static_assert(is_disjoint_from(None, Literal[True]))
static_assert(is_disjoint_from(None, Literal[1]))
static_assert(is_disjoint_from(None, Literal["test"]))
static_assert(is_disjoint_from(None, Literal[b"test"]))
static_assert(is_disjoint_from(None, LiteralString))
static_assert(is_disjoint_from(None, int))
static_assert(is_disjoint_from(None, type[object]))
static_assert(is_disjoint_from(None, AlwaysTruthy))

static_assert(not is_disjoint_from(None, None))
static_assert(not is_disjoint_from(None, object))
static_assert(not is_disjoint_from(None, AlwaysFalsy))
```

## Basic

```py
from typing_extensions import Literal, LiteralString
from knot_extensions import AlwaysFalsy, AlwaysTruthy, Intersection, Not, TypeOf, is_disjoint_from, static_assert

static_assert(is_disjoint_from(Literal[True], Literal[False]))
static_assert(is_disjoint_from(Literal[True], Literal[1]))
static_assert(is_disjoint_from(Literal[False], Literal[0]))

static_assert(is_disjoint_from(Literal[1], Literal[2]))

static_assert(is_disjoint_from(Literal["a"], Literal["b"]))

static_assert(is_disjoint_from(Literal[b"a"], LiteralString))
static_assert(is_disjoint_from(Literal[b"a"], Literal[b"b"]))
static_assert(is_disjoint_from(Literal[b"a"], Literal["a"]))

static_assert(is_disjoint_from(type[object], TypeOf[Literal]))
static_assert(is_disjoint_from(type[str], LiteralString))

static_assert(is_disjoint_from(AlwaysFalsy, AlwaysTruthy))

static_assert(not is_disjoint_from(Any, int))

static_assert(not is_disjoint_from(Literal[True], Literal[True]))
static_assert(not is_disjoint_from(Literal[False], Literal[False]))
static_assert(not is_disjoint_from(Literal[True], bool))
static_assert(not is_disjoint_from(Literal[True], int))

static_assert(not is_disjoint_from(Literal[1], Literal[1]))

static_assert(not is_disjoint_from(Literal["a"], Literal["a"]))
static_assert(not is_disjoint_from(Literal["a"], LiteralString))
static_assert(not is_disjoint_from(Literal["a"], str))

static_assert(not is_disjoint_from(int, int))
static_assert(not is_disjoint_from(LiteralString, LiteralString))

static_assert(not is_disjoint_from(str, LiteralString))
static_assert(not is_disjoint_from(str, type))
static_assert(not is_disjoint_from(str, type[Any]))
static_assert(not is_disjoint_from(str, AlwaysFalsy))
static_assert(not is_disjoint_from(str, AlwaysTruthy))
```

## Tuple types

```py
from typing_extensions import Literal
from knot_extensions import TypeOf, is_disjoint_from, static_assert

static_assert(is_disjoint_from(tuple[()], TypeOf[object]))
static_assert(is_disjoint_from(tuple[()], TypeOf[Literal]))

static_assert(is_disjoint_from(tuple[None], None))
static_assert(is_disjoint_from(tuple[None], Literal[b"a"]))
static_assert(is_disjoint_from(tuple[None], Literal["a"]))
static_assert(is_disjoint_from(tuple[None], Literal[1]))
static_assert(is_disjoint_from(tuple[None], Literal[True]))

static_assert(is_disjoint_from(tuple[Literal[1]], tuple[Literal[2]]))
static_assert(is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[Literal[1]]))
static_assert(is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[Literal[1], Literal[3]]))

static_assert(not is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[Literal[1], int]))
```

## Unions

```py
from typing_extensions import Literal
from knot_extensions import AlwaysFalsy, AlwaysTruthy, Intersection, is_disjoint_from, static_assert

static_assert(is_disjoint_from(Literal[1, 2], Literal[3]))
static_assert(is_disjoint_from(Literal[1, 2], Literal[3, 4]))
static_assert(is_disjoint_from(Literal[1, 2], AlwaysFalsy))

static_assert(is_disjoint_from(Intersection[int, Literal[1]], Literal[2]))

static_assert(not is_disjoint_from(Literal[1, 2], Literal[2]))
static_assert(not is_disjoint_from(Literal[1, 2], Literal[2, 3]))
static_assert(not is_disjoint_from(Literal[0, 1], AlwaysTruthy))

static_assert(not is_disjoint_from(Intersection[int, Literal[2]], Literal[2]))
```

## Class, module and function literals

```py
from types import ModuleType, FunctionType
from knot_extensions import TypeOf, is_disjoint_from, static_assert

class A: ...
class B: ...

static_assert(is_disjoint_from(TypeOf[A], TypeOf[B]))
static_assert(is_disjoint_from(TypeOf[A], type[B]))
static_assert(not is_disjoint_from(type[A], TypeOf[A]))
static_assert(not is_disjoint_from(type[A], type[B]))

import random
import math

static_assert(is_disjoint_from(TypeOf[random], TypeOf[math]))
static_assert(not is_disjoint_from(TypeOf[random], ModuleType))
static_assert(not is_disjoint_from(TypeOf[random], object))

def f(): ...
def g(): ...

static_assert(is_disjoint_from(TypeOf[f], TypeOf[g]))
static_assert(not is_disjoint_from(TypeOf[f], FunctionType))
static_assert(not is_disjoint_from(TypeOf[f], object))
```

## Module literals

```py
from knot_extensions import TypeOf, is_disjoint_from, static_assert
```

## Instance types versus `type[T]` types

Expand Down
190 changes: 0 additions & 190 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4537,196 +4537,6 @@ pub(crate) mod tests {
}
}

#[test_case(Ty::Never, Ty::Never)]
#[test_case(Ty::Never, Ty::None)]
#[test_case(Ty::Never, Ty::BuiltinInstance("int"))]
#[test_case(Ty::None, Ty::BooleanLiteral(true))]
#[test_case(Ty::None, Ty::IntLiteral(1))]
#[test_case(Ty::None, Ty::StringLiteral("test"))]
#[test_case(Ty::None, Ty::BytesLiteral("test"))]
#[test_case(Ty::None, Ty::LiteralString)]
#[test_case(Ty::None, Ty::BuiltinInstance("int"))]
#[test_case(Ty::None, Ty::Tuple(vec![Ty::None]))]
#[test_case(Ty::BooleanLiteral(true), Ty::BooleanLiteral(false))]
#[test_case(Ty::BooleanLiteral(true), Ty::Tuple(vec![Ty::None]))]
#[test_case(Ty::BooleanLiteral(true), Ty::IntLiteral(1))]
#[test_case(Ty::BooleanLiteral(false), Ty::IntLiteral(0))]
#[test_case(Ty::IntLiteral(1), Ty::IntLiteral(2))]
#[test_case(Ty::IntLiteral(1), Ty::Tuple(vec![Ty::None]))]
#[test_case(Ty::StringLiteral("a"), Ty::StringLiteral("b"))]
#[test_case(Ty::StringLiteral("a"), Ty::Tuple(vec![Ty::None]))]
#[test_case(Ty::LiteralString, Ty::BytesLiteral("a"))]
#[test_case(Ty::BytesLiteral("a"), Ty::BytesLiteral("b"))]
#[test_case(Ty::BytesLiteral("a"), Ty::Tuple(vec![Ty::None]))]
#[test_case(Ty::BytesLiteral("a"), Ty::StringLiteral("a"))]
#[test_case(Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::IntLiteral(3))]
#[test_case(Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::Union(vec![Ty::IntLiteral(3), Ty::IntLiteral(4)]))]
#[test_case(Ty::Intersection{pos: vec![Ty::BuiltinInstance("int"), Ty::IntLiteral(1)], neg: vec![]}, Ty::IntLiteral(2))]
#[test_case(Ty::Tuple(vec![Ty::IntLiteral(1)]), Ty::Tuple(vec![Ty::IntLiteral(2)]))]
#[test_case(Ty::Tuple(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::Tuple(vec![Ty::IntLiteral(1)]))]
#[test_case(Ty::Tuple(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::Tuple(vec![Ty::IntLiteral(1), Ty::IntLiteral(3)]))]
#[test_case(Ty::Tuple(vec![]), Ty::BuiltinClassLiteral("object"))]
#[test_case(Ty::SubclassOfBuiltinClass("object"), Ty::None)]
#[test_case(Ty::SubclassOfBuiltinClass("str"), Ty::LiteralString)]
#[test_case(Ty::AlwaysFalsy, Ty::AlwaysTruthy)]
#[test_case(Ty::Tuple(vec![]), Ty::TypingLiteral)]
#[test_case(Ty::TypingLiteral, Ty::SubclassOfBuiltinClass("object"))]
fn is_disjoint_from(a: Ty, b: Ty) {
let db = setup_db();
let a = a.into_type(&db);
let b = b.into_type(&db);

assert!(a.is_disjoint_from(&db, b));
assert!(b.is_disjoint_from(&db, a));
}

#[test_case(Ty::Any, Ty::BuiltinInstance("int"))]
#[test_case(Ty::None, Ty::None)]
#[test_case(Ty::None, Ty::BuiltinInstance("object"))]
#[test_case(Ty::BuiltinInstance("int"), Ty::BuiltinInstance("int"))]
#[test_case(Ty::BuiltinInstance("str"), Ty::LiteralString)]
#[test_case(Ty::BooleanLiteral(true), Ty::BooleanLiteral(true))]
#[test_case(Ty::BooleanLiteral(false), Ty::BooleanLiteral(false))]
#[test_case(Ty::BooleanLiteral(true), Ty::BuiltinInstance("bool"))]
#[test_case(Ty::BooleanLiteral(true), Ty::BuiltinInstance("int"))]
#[test_case(Ty::IntLiteral(1), Ty::IntLiteral(1))]
#[test_case(Ty::StringLiteral("a"), Ty::StringLiteral("a"))]
#[test_case(Ty::StringLiteral("a"), Ty::LiteralString)]
#[test_case(Ty::StringLiteral("a"), Ty::BuiltinInstance("str"))]
#[test_case(Ty::LiteralString, Ty::LiteralString)]
#[test_case(Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::IntLiteral(2))]
#[test_case(Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::Union(vec![Ty::IntLiteral(2), Ty::IntLiteral(3)]))]
#[test_case(Ty::Intersection{pos: vec![Ty::BuiltinInstance("int"), Ty::IntLiteral(2)], neg: vec![]}, Ty::IntLiteral(2))]
#[test_case(Ty::Tuple(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::Tuple(vec![Ty::IntLiteral(1), Ty::BuiltinInstance("int")]))]
#[test_case(Ty::BuiltinClassLiteral("str"), Ty::BuiltinInstance("type"))]
#[test_case(Ty::BuiltinClassLiteral("str"), Ty::SubclassOfAny)]
#[test_case(Ty::AbcClassLiteral("ABC"), Ty::AbcInstance("ABCMeta"))]
#[test_case(Ty::BuiltinInstance("str"), Ty::AlwaysTruthy)]
#[test_case(Ty::BuiltinInstance("str"), Ty::AlwaysFalsy)]
fn is_not_disjoint_from(a: Ty, b: Ty) {
let db = setup_db();
let a = a.into_type(&db);
let b = b.into_type(&db);

assert!(!a.is_disjoint_from(&db, b));
assert!(!b.is_disjoint_from(&db, a));
}

#[test]
fn is_disjoint_from_union_of_class_types() {
let mut db = setup_db();
db.write_dedented(
"/src/module.py",
"
class A: ...
class B: ...
U = A if flag else B
",
)
.unwrap();
let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();

let type_a = super::global_symbol(&db, module, "A").expect_type();
let type_u = super::global_symbol(&db, module, "U").expect_type();

assert!(type_a.is_class_literal());
assert!(type_u.is_union());

assert!(!type_a.is_disjoint_from(&db, type_u));
}

#[test]
fn is_disjoint_type_subclass_of() {
let mut db = setup_db();
db.write_dedented(
"/src/module.py",
"
class A: ...
class B: ...
",
)
.unwrap();
let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();

let literal_a = super::global_symbol(&db, module, "A").expect_type();
let literal_b = super::global_symbol(&db, module, "B").expect_type();

let subclass_of_a = SubclassOfType::from(&db, literal_a.expect_class_literal().class);
let subclass_of_b = SubclassOfType::from(&db, literal_b.expect_class_literal().class);

// Class literals are always disjoint. They are singleton types
assert!(literal_a.is_disjoint_from(&db, literal_b));

// The class A is a subclass of A, so A is not disjoint from type[A]
assert!(!literal_a.is_disjoint_from(&db, subclass_of_a));

// The class A is disjoint from type[B] because it's not a subclass
// of B:
assert!(literal_a.is_disjoint_from(&db, subclass_of_b));

// However, type[A] is not disjoint from type[B], as there could be
// classes that inherit from both A and B:
assert!(!subclass_of_a.is_disjoint_from(&db, subclass_of_b));
}

#[test]
fn is_disjoint_module_literals() {
let mut db = setup_db();
db.write_dedented(
"/src/module.py",
"
import random
import math
",
)
.unwrap();

let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();

let module_literal_random = super::global_symbol(&db, module, "random").expect_type();
let module_literal_math = super::global_symbol(&db, module, "math").expect_type();

assert!(module_literal_random.is_disjoint_from(&db, module_literal_math));

assert!(!module_literal_random.is_disjoint_from(
&db,
Ty::KnownClassInstance(KnownClass::ModuleType).into_type(&db)
));
assert!(!module_literal_random.is_disjoint_from(
&db,
Ty::KnownClassInstance(KnownClass::Object).into_type(&db)
));
}

#[test]
fn is_disjoint_function_literals() {
let mut db = setup_db();
db.write_dedented(
"/src/module.py",
"
def f(): ...
def g(): ...
",
)
.unwrap();

let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();

let function_literal_f = super::global_symbol(&db, module, "f").expect_type();
let function_literal_g = super::global_symbol(&db, module, "g").expect_type();

assert!(function_literal_f.is_disjoint_from(&db, function_literal_g));

assert!(!function_literal_f.is_disjoint_from(
&db,
Ty::KnownClassInstance(KnownClass::FunctionType).into_type(&db)
));
assert!(!function_literal_f.is_disjoint_from(
&db,
Ty::KnownClassInstance(KnownClass::Object).into_type(&db)
));
}

/// Explicitly test for Python version <3.13 and >=3.13, to ensure that
/// the fallback to `typing_extensions` is working correctly.
/// See [`KnownClass::canonical_module`] for more information.
Expand Down
15 changes: 15 additions & 0 deletions crates/red_knot_python_semantic/src/types/property_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,4 +417,19 @@ mod flaky {
two_equivalent_types_are_also_gradual_equivalent, db,
forall types s, t. s.is_equivalent_to(db, t) => s.is_gradual_equivalent_to(db, t)
);

// Two gradual equivalent fully static types are also equivalent.
type_property_test!(
two_gradual_equivalent_fully_static_types_are_also_equivalent, db,
forall types s, t.
s.is_fully_static(db) && s.is_gradual_equivalent_to(db, t) => s.is_equivalent_to(db, t)
);

// `S | T` is always a supertype of `S`.
// Thus, `S` is never disjoint from `S | T`.
type_property_test!(
constituent_members_of_union_is_not_disjoint_from_that_union, db,
forall types s, t.
!s.is_disjoint_from(db, union(db, s, t)) && !t.is_disjoint_from(db, union(db, s, t))
);
}
Loading