Skip to content

Commit 80712dc

Browse files
committed
more tests
1 parent 4490fe8 commit 80712dc

File tree

3 files changed

+142
-19
lines changed

3 files changed

+142
-19
lines changed

crates/ty_python_semantic/resources/mdtest/exhaustiveness_checking.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,22 @@ def match_non_exhaustive(x: Literal[0, 1, "a"]):
7474

7575
# this diagnostic is correct: the inferred type of `x` is `Literal[1]`
7676
assert_never(x) # error: [type-assertion-failure]
77+
78+
# This is based on real-world code:
79+
# https://github.com/scipy/scipy/blob/99c0ef6af161a4d8157cae5276a20c30b7677c6f/scipy/linalg/tests/test_lapack.py#L147-L171
80+
def exhaustiveness_using_containment_checks():
81+
for norm_str in "Mm1OoIiFfEe":
82+
if norm_str in "FfEe":
83+
return
84+
else:
85+
if norm_str in "Mm":
86+
return
87+
elif norm_str in "1Oo":
88+
return
89+
elif norm_str in "Ii":
90+
return
91+
92+
assert_never(norm_str)
7793
```
7894

7995
## Checks on enum literals

crates/ty_python_semantic/resources/mdtest/unpacking.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,112 @@ reveal_type(a) # revealed: Literal["�"]
654654
reveal_type(b) # revealed: Literal["�"]
655655
```
656656

657+
### Very long literal
658+
659+
```py
660+
string = "very long stringgggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg"
661+
662+
a, *b = string
663+
reveal_type(a) # revealed: LiteralString
664+
reveal_type(b) # revealed: list[LiteralString]
665+
```
666+
667+
## Bytes
668+
669+
### Simple unpacking
670+
671+
```py
672+
a, b = b"ab"
673+
reveal_type(a) # revealed: Literal[97]
674+
reveal_type(b) # revealed: Literal[98]
675+
```
676+
677+
### Uneven unpacking (1)
678+
679+
```py
680+
# error: [invalid-assignment] "Not enough values to unpack: Expected 3"
681+
a, b, c = b"ab"
682+
reveal_type(a) # revealed: Unknown
683+
reveal_type(b) # revealed: Unknown
684+
reveal_type(c) # revealed: Unknown
685+
```
686+
687+
### Uneven unpacking (2)
688+
689+
```py
690+
# error: [invalid-assignment] "Too many values to unpack: Expected 2"
691+
a, b = b"abc"
692+
reveal_type(a) # revealed: Unknown
693+
reveal_type(b) # revealed: Unknown
694+
```
695+
696+
### Starred expression (1)
697+
698+
```py
699+
# error: [invalid-assignment] "Not enough values to unpack: Expected at least 3"
700+
(a, *b, c, d) = b"ab"
701+
reveal_type(a) # revealed: Unknown
702+
reveal_type(b) # revealed: list[Unknown]
703+
reveal_type(c) # revealed: Unknown
704+
reveal_type(d) # revealed: Unknown
705+
```
706+
707+
```py
708+
# error: [invalid-assignment] "Not enough values to unpack: Expected at least 3"
709+
(a, b, *c, d) = b"a"
710+
reveal_type(a) # revealed: Unknown
711+
reveal_type(b) # revealed: Unknown
712+
reveal_type(c) # revealed: list[Unknown]
713+
reveal_type(d) # revealed: Unknown
714+
```
715+
716+
### Starred expression (2)
717+
718+
```py
719+
(a, *b, c) = b"ab"
720+
reveal_type(a) # revealed: Literal[97]
721+
reveal_type(b) # revealed: list[Never]
722+
reveal_type(c) # revealed: Literal[98]
723+
```
724+
725+
### Starred expression (3)
726+
727+
```py
728+
(a, *b, c) = b"abc"
729+
reveal_type(a) # revealed: Literal[97]
730+
reveal_type(b) # revealed: list[Literal[98]]
731+
reveal_type(c) # revealed: Literal[99]
732+
```
733+
734+
### Starred expression (4)
735+
736+
```py
737+
(a, *b, c, d) = b"abcdef"
738+
reveal_type(a) # revealed: Literal[97]
739+
reveal_type(b) # revealed: list[Literal[98, 99, 100]]
740+
reveal_type(c) # revealed: Literal[101]
741+
reveal_type(d) # revealed: Literal[102]
742+
```
743+
744+
### Starred expression (5)
745+
746+
```py
747+
(a, b, *c) = b"abcd"
748+
reveal_type(a) # revealed: Literal[97]
749+
reveal_type(b) # revealed: Literal[98]
750+
reveal_type(c) # revealed: list[Literal[99, 100]]
751+
```
752+
753+
### Very long literal
754+
755+
```py
756+
too_long = b"very long bytes stringggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg"
757+
758+
a, *b = too_long
759+
reveal_type(a) # revealed: int
760+
reveal_type(b) # revealed: list[int]
761+
```
762+
657763
## Union
658764

659765
### Same types

crates/ty_python_semantic/src/types.rs

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4974,16 +4974,12 @@ impl<'db> Type<'db> {
49744974
};
49754975
}
49764976

4977-
match self {
4978-
Type::NominalInstance(nominal) => {
4979-
if let Some(spec) = nominal.tuple_spec(db) {
4980-
return Ok(spec);
4981-
}
4982-
}
4977+
let special_case = match self {
4978+
Type::NominalInstance(nominal) => nominal.tuple_spec(db),
49834979
Type::GenericAlias(alias) if alias.origin(db).is_tuple(db) => {
4984-
return Ok(Cow::Owned(TupleSpec::homogeneous(todo_type!(
4980+
Some(Cow::Owned(TupleSpec::homogeneous(todo_type!(
49854981
"*tuple[] annotations"
4986-
))));
4982+
))))
49874983
}
49884984
Type::StringLiteral(string_literal_ty) => {
49894985
let string_literal = string_literal_ty.value(db);
@@ -4994,9 +4990,9 @@ impl<'db> Type<'db> {
49944990
.map(|c| Type::string_literal(db, &c.to_string())),
49954991
)
49964992
} else {
4997-
TupleSpec::homogeneous(self)
4993+
TupleSpec::homogeneous(Type::LiteralString)
49984994
};
4999-
return Ok(Cow::Owned(spec));
4995+
Some(Cow::Owned(spec))
50004996
}
50014997
Type::BytesLiteral(bytes) => {
50024998
let bytes_literal = bytes.value(db);
@@ -5009,33 +5005,35 @@ impl<'db> Type<'db> {
50095005
} else {
50105006
TupleSpec::homogeneous(KnownClass::Int.to_instance(db))
50115007
};
5012-
return Ok(Cow::Owned(spec));
5008+
Some(Cow::Owned(spec))
50135009
}
50145010
Type::Never => {
50155011
// The dunder logic below would have us return `tuple[Never, ...]`, which eagerly
50165012
// simplifies to `tuple[()]`. That will will cause us to emit false positives if we
50175013
// index into the tuple. Using `tuple[Unknown, ...]` avoids these false positives.
50185014
// TODO: Consider removing this special case, and instead hide the indexing
50195015
// diagnostic in unreachable code.
5020-
return Ok(Cow::Owned(TupleSpec::homogeneous(Type::unknown())));
5016+
Some(Cow::Owned(TupleSpec::homogeneous(Type::unknown())))
50215017
}
50225018
Type::TypeAlias(alias) => {
5023-
return alias.value_type(db).try_iterate_with_mode(db, mode);
5019+
Some(alias.value_type(db).try_iterate_with_mode(db, mode)?)
50245020
}
50255021
Type::NonInferableTypeVar(tvar) => match tvar.typevar(db).bound_or_constraints(db) {
50265022
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
5027-
return bound.try_iterate_with_mode(db, mode);
5023+
Some(bound.try_iterate_with_mode(db, mode)?)
50285024
}
50295025
// TODO: could we create a "union of tuple specs"...?
50305026
// (Same question applies to the `Type::Union()` branch lower down)
5031-
Some(TypeVarBoundOrConstraints::Constraints(_)) | None => {}
5027+
Some(TypeVarBoundOrConstraints::Constraints(_)) | None => None
50325028
},
50335029
Type::TypeVar(_) => unreachable!(
50345030
"should not be able to iterate over type variable {} in inferable position",
50355031
self.display(db)
50365032
),
5037-
Type::Dynamic(_)
5038-
| Type::FunctionLiteral(_)
5033+
// N.B. These special cases aren't strictly necessary, they're just obvious optimizations
5034+
Type::LiteralString | Type::Dynamic(_) => Some(Cow::Owned(TupleSpec::homogeneous(self))),
5035+
5036+
Type::FunctionLiteral(_)
50395037
| Type::GenericAlias(_)
50405038
| Type::BoundMethod(_)
50415039
| Type::MethodWrapper(_)
@@ -5061,10 +5059,13 @@ impl<'db> Type<'db> {
50615059
| Type::IntLiteral(_)
50625060
| Type::BooleanLiteral(_)
50635061
| Type::EnumLiteral(_)
5064-
| Type::LiteralString
50655062
| Type::BoundSuper(_)
50665063
| Type::TypeIs(_)
5067-
| Type::TypedDict(_) => {}
5064+
| Type::TypedDict(_) => None
5065+
};
5066+
5067+
if let Some(special_case) = special_case {
5068+
return Ok(special_case);
50685069
}
50695070

50705071
let try_call_dunder_getitem = || {

0 commit comments

Comments
 (0)