basedpyright icon indicating copy to clipboard operation
basedpyright copied to clipboard

narrowing does not consider overlapping union members

Open KotlinIsland opened this issue 6 months ago • 9 comments

seo

disjoint

Description

Code sample in basedpyright playground

from typing import reveal_type


class Foo: ...

class FooBar(Foo, list[int]): ...

def f(x: Foo | list[str]):
    if isinstance(x, list):
        reveal_type(x[0]) # str, expect object

f(FooBar(1))

KotlinIsland avatar Aug 26 '25 03:08 KotlinIsland

I think multiple inheritance brings a lot of issues with it. Is it worth taking away valuable narrowing from people who try to avoid multiple inheritance?

beauxq avatar Aug 27 '25 12:08 beauxq

It would probably need to be a setting so users can decide for themselves, similar to what I did with strictGenericNarrowing

DetachHead avatar Aug 28 '25 01:08 DetachHead

I think multiple inheritance brings a lot of issues with it. Is it worth taking away valuable narrowing from people who try to avoid multiple inheritance?

@beauxq if multiple inheritance were to be banned, what would that look like to you. as python does not have constructs like interface/trait, i would imagine that a change like this would result in a lot of patterns becomming unusable

KotlinIsland avatar Sep 20 '25 05:09 KotlinIsland

I'm not sure how to describe what it would look like to me, because to me it just looks pretty normal.

Just the other day, I thought "I could use multiple inheritance for this." But because I try to avoid multiple inheritance, I thought about it a little longer, and I found a good solution that didn't involve multiple inheritance.

Yes, a lot of patterns become unusable. But the same can be said about usage of static type checking in general. For example, this is a pattern that many people have found useful: (from a real code base that I deal with)

import sys
module = sys.modules[__name__]
for name, value in arguments.__dict__.items():
    setattr(module, name, value)

But there's just no way to get good static type checking around that.

You might be tempted to point at some code that uses multiple inheritance, and say "How would you do that without multiple inheritance?" But to me, that's like someone asking pointing at this code here, and saying "How would you type check that?" The answer is: I wouldn't have written anything remotely close to that to begin with.

We decide to use different patterns, so that we can get better help from our tools.

And I don't think the person who wrote this code, or the people who use multiple inheritance, are doing something wrong. It's just not worth it for me personally.

beauxq avatar Sep 20 '25 20:09 beauxq

class ImARealClass:
    name = "amongus

class ImAnInterface:
    def do_something():
        print("hi")

class ImAnImplementation(ImARealClass, ImAnInterface):  # illegal multiple inheritance
    pass

i haven't put the thought in to realize it, but what if we had some kind of @interface decorator or something that could be used to allow multiple inheritance while still protecting against non-disjoint cases?

KotlinIsland avatar Sep 21 '25 03:09 KotlinIsland

sounds like my @implements decorator hack is relevant here? https://github.com/DetachHead/basedpyright/issues/775#issuecomment-2807917771

DetachHead avatar Sep 21 '25 06:09 DetachHead

@beauxq i agree with your logic, i feel the same way when arguing against circular imports. i always tell people they can typically be avoided if you just think a bit more about what you're doing.

though in my experience avoiding multiple inheritance in python has been a lot harder than avoiding circular imports. so i'm curious if you could provide any examples of how you avoided them

DetachHead avatar Sep 21 '25 06:09 DetachHead

Often it involves favoring composition over inheritance (which is a good idea even if you're not trying to avoid multiple inheritance).

Another common way is using Protocol, since it doesn't require inheritance (keeping in mind that @runtime_checkable isn't safe, so avoiding that).

beauxq avatar Sep 26 '25 15:09 beauxq

keeping in mind that @runtime_checkable isn't safe, so avoiding that

we have an open issue somewhere to report a warning on usage of @runtine_checkable

KotlinIsland avatar Sep 27 '25 06:09 KotlinIsland