@@ -238,3 +238,103 @@ def match_non_exhaustive(x: A | B | C):
238238 # this diagnostic is correct: the inferred type of `x` is `B & ~A & ~C`
239239 assert_never(x) # error: [type-assertion-failure]
240240```
241+
242+ ## ` isinstance ` checks with generics
243+
244+ ``` toml
245+ [environment ]
246+ python-version = " 3.12"
247+ ```
248+
249+ ``` py
250+ from typing import assert_never
251+
252+ class A[T]: ...
253+ class ASub[T](A[T]): ...
254+ class B[T]: ...
255+ class C[T]: ...
256+ class D : ...
257+ class E : ...
258+ class F : ...
259+
260+ def if_else_exhaustive (x : A[D] | B[E] | C[F]):
261+ if isinstance (x, A):
262+ pass
263+ elif isinstance (x, B):
264+ pass
265+ elif isinstance (x, C):
266+ pass
267+ else :
268+ # TODO : both of these are false positives (https://github.com/astral-sh/ty/issues/456)
269+ no_diagnostic_here # error: [unresolved-reference]
270+ assert_never(x) # error: [type-assertion-failure]
271+
272+ # TODO : false-positive diagnostic (https://github.com/astral-sh/ty/issues/456)
273+ def if_else_exhaustive_no_assertion (x : A[D] | B[E] | C[F]) -> int : # error: [invalid-return-type]
274+ if isinstance (x, A):
275+ return 0
276+ elif isinstance (x, B):
277+ return 1
278+ elif isinstance (x, C):
279+ return 2
280+
281+ def if_else_non_exhaustive (x : A[D] | B[E] | C[F]):
282+ if isinstance (x, A):
283+ pass
284+ elif isinstance (x, C):
285+ pass
286+ else :
287+ this_should_be_an_error # error: [unresolved-reference]
288+
289+ # this diagnostic is correct: the inferred type of `x` is `B[E] & ~A[D] & ~C[F]`
290+ assert_never(x) # error: [type-assertion-failure]
291+
292+ def match_exhaustive (x : A[D] | B[E] | C[F]):
293+ match x:
294+ case A():
295+ pass
296+ case B():
297+ pass
298+ case C():
299+ pass
300+ case _:
301+ # TODO : both of these are false positives (https://github.com/astral-sh/ty/issues/456)
302+ no_diagnostic_here # error: [unresolved-reference]
303+ assert_never(x) # error: [type-assertion-failure]
304+
305+ # TODO : false-positive diagnostic (https://github.com/astral-sh/ty/issues/456)
306+ def match_exhaustive_no_assertion (x : A[D] | B[E] | C[F]) -> int : # error: [invalid-return-type]
307+ match x:
308+ case A():
309+ return 0
310+ case B():
311+ return 1
312+ case C():
313+ return 2
314+
315+ def match_non_exhaustive (x : A[D] | B[E] | C[F]):
316+ match x:
317+ case A():
318+ pass
319+ case C():
320+ pass
321+ case _:
322+ this_should_be_an_error # error: [unresolved-reference]
323+
324+ # this diagnostic is correct: the inferred type of `x` is `B[E] & ~A[D] & ~C[F]`
325+ assert_never(x) # error: [type-assertion-failure]
326+
327+ # This function might seem a bit silly, but it's a pattern that exists in real-world code!
328+ # see https://github.com/bokeh/bokeh/blob/adef0157284696ce86961b2089c75fddda53c15c/src/bokeh/core/property/container.py#L130-L140
329+ def no_invalid_return_diagnostic_here_either[T](x: A[T]) -> ASub[T]:
330+ if isinstance (x, A):
331+ if isinstance (x, ASub):
332+ return x
333+ else :
334+ return ASub()
335+ else :
336+ # We *would* emit a diagnostic here complaining that it's an invalid `return` statement
337+ # ...except that we (correctly) infer that this branch is unreachable, so the complaint
338+ # is null and void (and therefore we don't emit a diagnostic)
339+ return x
340+ ```
0 commit comments