astroid icon indicating copy to clipboard operation
astroid copied to clipboard

Treat NewTypes like normal subclasses

Open colatkinson opened this issue 4 years ago • 6 comments

Steps

  • [X] For new features or bug fixes, add a ChangeLog entry describing what your PR does.
  • [X] Write a good description on what the PR does.

Description

NewTypes are assumed not to inherit any members from their base classes. This results in incorrect inference results. Avoid this by changing the transformation for NewTypes to treat them like any other subclass.

Type of Changes

Type
:bug: Bug fix
:sparkles: New feature
:hammer: Refactoring
:scroll: Docs

Related Issue

https://github.com/PyCQA/pylint/issues/3162 https://github.com/PyCQA/pylint/issues/2296

colatkinson avatar Dec 17 '21 07:12 colatkinson

Switched from checking for bit_count() to bit_length(), as I didn't realize the former was introduced in 3.10.

colatkinson avatar Dec 18 '21 08:12 colatkinson

Made the requested changes -- let me know if I missed any! I also changed the transformation a bit, so that it takes in the correct number of arguments, and beefed up the tests a bit, both here and in the pylint PR (PyCQA/pylint#5542).

I also had to change the transformation to accurately resolve references to user-defined and imported types, so adding those tests was definitely important.

colatkinson avatar Dec 23 '21 10:12 colatkinson

Hey sorry about the delay. I haven't had a whole lot of bandwidth to work on this, but I'm hoping to be able to get back to it this week. Just a heads up it hasn't totally fallen off my radar!

colatkinson avatar Feb 07 '22 08:02 colatkinson

Pull Request Test Coverage Report for Build 3508001866

  • 81 of 83 (97.59%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.08%) to 92.367%

Changes Missing Coverage Covered Lines Changed/Added Lines %
astroid/brain/brain_typing.py 81 83 97.59%
<!-- Total: 81 83
Totals Coverage Status
Change from base Build 3503977238: 0.08%
Covered Lines: 9947
Relevant Lines: 10769

💛 - Coveralls

coveralls avatar May 28 '22 11:05 coveralls

@cdce8p Thanks for taking a look! The feedback from you and @DanielNoord has been super helpful throughout this process.

Your point about the divergence of the inferred and runtime types make sense to me. I modeled the implementation on the one currently in the main branch, which leaves things in roughly the same state (inferring a class that doesn't exist at runtime), but that's kind of the source of all this trouble in the first place :wink:.

Do you have any thoughts or pointers on a more correct implementation? My initial thought would be a simple identity function, e.g.

def ChildType(x):
    return x

Do you think that would be sufficient? It could be a bit strange that the code would infer a function, which would then be referenced by type annotations elsewhere, but I'm not sure if this presents any problems in practice.

It might also make sense to have the inferred implementation be identical to the upstream CPython one you linked -- though in that case, is any transformation even necessary? Based on a quick test, the results come back as Uninferable without any transforms, but I'm not sure if there's a lighter-touch way around that.

Finally, with regards to creating a new PR: I was planning on keeping the current implementation around in the git history. Though if a new PR is easier for you and other maintainers, or if the project generally makes use of squash commits, I can definitely do that instead.

colatkinson avatar Jun 14 '22 10:06 colatkinson

Do you have any thoughts or pointers on a more correct implementation? My initial thought would be a simple identity function, e.g.

def ChildType(x):
    return x

Do you think that would be sufficient? It could be a bit strange that the code would infer a function, which would then be referenced by type annotations elsewhere, but I'm not sure if this presents any problems in practice.

An identity function might work, but I would patch ChildType. Instead it might be better to replace the NewType inferred function itself. That could also solve the type annotation issue. Basically the inferred type should become x instead of ChildType. You just don't get the subclassing.

Tbh though, I haven't had time to investigate if it's at all possible.

Finally, with regards to creating a new PR: I was planning on keeping the current implementation around in the git history. Though if a new PR is easier for you and other maintainers, or if the project generally makes use of squash commits, I can definitely do that instead.

We do use squash merge usually. However, my reasoning was more from a practical approach. No unrelated comments and if everything else fails, we can still come back to this one.

I'll convert the PR to a draft. We can close it once the alternative is merged.

cdce8p avatar Jun 22 '22 00:06 cdce8p