pyrefly icon indicating copy to clipboard operation
pyrefly copied to clipboard

Make None generalise to Optional[T]

Open ndmitchell opened this issue 6 months ago • 3 comments

Describe the Bug

In most cases if we see None we should treat that as None | ?, perhaps just in an assignment

CC @stroxler

Sandbox Link

No response

(Only applicable for extension issues) IDE Information

No response

ndmitchell avatar May 17 '25 12:05 ndmitchell

https://pyrefly.org/sandbox/?code=MQAg6gpgNgxg9gWwiALnVALZAFAngJwgDMpcQBlAQwDsATAIzgA8AaTASwGcQuRKQADpQDmEAFCgA7lkIhccAK4gYNEJPzsUENpwyVZNWiCgR91PowUpUuAe2rCe5vCgxxqAOgkSQACQgGBsqKAiYgRKYoCoTccEQc3JyGjEwAXD4AtCAA4hDWhJRQGSjsSDYCyDBYMADW9o4REAyUtZkgAKqcyIQAbqZQAPoothAAFACUqOj2nBUw1vYR+IRGwxWcbRBMFRpI1NaSmhiYyACSACIAouGR0RDcrpQHyJwKAgJw+NajEB7CHnwrHB4AhQnltCBhHBitDaMR7Jp2O42G4+vg2HkYOM2mANFo+HQQPgFM5cK53ME4SBaOxCPNSE45Ip8CB6Pg4JIuvgxGIiOyEOV6jxQZ9rAAqHlw+JaTgoUZMAC8ADl3BBxukQJqiRA+oUhiN5eMgA

When we have an unannotated param with default=None, we infer Any, but I think typeshed would like us to infer Any | None

https://github.com/python/typeshed/issues/14029

yangdanny97 avatar May 18 '25 11:05 yangdanny97

@yangdanny97 - agreed, but I actually think that is a separate issue (but also worth fixing). For any default=Y we should infer Any | TYPE[Y].

ndmitchell avatar May 27 '25 19:05 ndmitchell

@ndmitchell just wanna make sure i'm on the right track here...

Seems like we already support this for simple assignments to variables:

https://pyrefly.org/sandbox/?code=GYexC4AICMwG0gXkgFQE4FcCmAoAHkpAIw4CWwkoEOktkByAciAHa5pYBuWAhnAPoAXAJ4AHLAAo8AShxA

Jon's feedback seemed related to inserting None into containers, which I don't think could be solved by searching for a common parent class since it would be object in this case.

I see that override checks for class vars are a problem when the parent class sets it as None

https://pyrefly.org/sandbox/?code=MYGwhgzhAEBiD28BcAoa7oA9oF5oDl4A7AUxRVEhgCEwAnACgXgEpUMtdoBGFIA

Mypy complains like we do, but Pyright allows this pattern even though it's not sound (below is Pyright's reveal_type behavior)

class Foo:
    x = None

class Bar(Foo):
    x = 1

reveal_type(Bar.x) # int
reveal_type(Foo.x) # None
def test(x: type[Foo]):
    reveal_type(x.x) # None

yangdanny97 avatar May 29 '25 14:05 yangdanny97

Here's a non-inheritance example:

class C:
    x = None
    def __init__(self, x: int | None):
        if x:
            self.x = x

c = C(None)
reveal_type(c.x)

yangdanny97 avatar Jun 05 '25 14:06 yangdanny97

This issue has someone assigned, but has not had recent activity for more than 2 weeks.

If you are still working on this issue, please add a comment so everyone knows. Otherwise, please unassign yourself and allow someone else to take over.

Thank you for your contributions!

github-actions[bot] avatar Jul 06 '25 00:07 github-actions[bot]

I think there's 2 pieces to this:

  1. make un-annotated fields inherit annotations from the parent class (I'm not sure which one they should inherit if there's multiple inheritance, but perhaps any of them will suffice). this should always happen
  2. if a field is un-annotated and doesn't inherit any annotations we can infer Any. this should be gated behind a flag.

edit: we may already have no. 1 implemented

yangdanny97 avatar Aug 15 '25 15:08 yangdanny97

This issue has someone assigned, but has not had recent activity for more than 2 weeks.

If you are still working on this issue, please add a comment so everyone knows. Otherwise, please unassign yourself and allow someone else to take over.

Thank you for your contributions!

github-actions[bot] avatar Aug 30 '25 00:08 github-actions[bot]