Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Factory class methods that return instance are hinted incorrectly when called explicitly from subclass #395

Closed
juanpablos opened this issue Sep 20, 2020 · 4 comments
Labels
bug Something isn't working fixed in next version (main) A fix has been implemented and will appear in an upcoming version

Comments

@juanpablos
Copy link

Environment data

  • Language Server version: 2020.9.5
  • OS and version: Windows 10
  • Python version (& distribution if applicable, e.g. Anaconda): Python 3.8.5

Expected behaviour

Having a class method that returns an instance of the class, if the class method is called from the subclass the type hint should be the subclass.

Actual behaviour

The type hint is the super class, where the class method is defined.

Code Snippet / Additional information

Here is a trivial example that shows the issue.

T = TypeVar('T', bound='A')

class A:
	@classmethod
	def factory(cls: Type[T]) -> T:
		return cls()

class B(A):
	@classmethod
	def factoryB(cls):
		# with super().factory() happens the same
		return super(B, cls).factory()


# these are OK
a = A() # this is A, hinted as A
a_factory = A.factory() # this is A, hinted as A
b = B() # this is B, hinted as B
b_factory = B.factory() # this is B, hinted as B

# this should be hinted as B
b_factoryB = B.factoryB() # this is B, but it is hinted as A

What I am trying to do is actually override the class method and call the super class one with additional arguments. But I noticed calling the class method of the super class explicitly type hints incorrectly.

@erictraut
Copy link
Contributor

Thanks for the bug report. You are correct. Pylance was not correctly handling the second argument of the two-argument form of the super call. This will be fixed in the next release.

@erictraut erictraut added bug Something isn't working fixed in next version (main) A fix has been implemented and will appear in an upcoming version labels Sep 21, 2020
@github-actions github-actions bot removed the triage label Sep 21, 2020
@jakebailey
Copy link
Member

This issue has been fixed in version 2020.9.6, which we've just released. You can find the changelog here: https://github.com/microsoft/pylance-release/blob/master/CHANGELOG.md#202096-23-september-2020

juanpablos added a commit to juanpablos/GNN-explain that referenced this issue Sep 24, 2020
@eddy-geek
Copy link

I confirm this works (with 2021.3.0 now)

But this looks to me like a workaround -- why do we need to explicitly bind and annotate cls: Type[T]?

Without it, pylance inferred type for factory is (cls: Type[A]) -> A instead of the generic one.
I expect pylance to infer the generic bounds (and then infer B.factory() will return a B) on its own?

My very similar motivating example:

class A:
    @classmethod
    def factory(cls):
        return cls()

class B(A):
    def methodB(self):
        pass

B().factory().methodB()

Output: Cannot access member "methodB" for class "A"

@erictraut
Copy link
Contributor

Internally, Pylance synthesizes generic types for self and cls if they don't have explicit type annotations. In this case, the synthesized type for cls is a type variable that is bound to A. (We don't display this synthesized TypeVar in the hover text because it would be confusing, so it simply appears as A.)

As for the return type of the factory method, that needs to be inferred because there is no specified return type annotation. If you were to change the method body to simply return cls (without the parentheses), you would see that the inferred type of the expression B.factory() is Type[B]. But when cls is instantiated, it's being specialized to its bound type A, so the inferred type of the expression B.factory() is A. I agree that there's no need in this case to specialize the resulting instance, so I've changed the code to retain the generic type for purposes of return type inference. With this change, it will act as you expect. The inferred type of the expression B.factory() is now B. This change will be in the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working fixed in next version (main) A fix has been implemented and will appear in an upcoming version
Projects
None yet
Development

No branches or pull requests

4 participants