ronaldoussoren / pyobjc

The Python <-> Objective-C Bridge with bindings for macOS frameworks
https://pyobjc.readthedocs.io
523 stars 45 forks source link

Teach metadata tooling about type stubs #441

Open ronaldoussoren opened 2 years ago

ronaldoussoren commented 2 years ago

Current plan for static typing is to generate type stubs (".pyi" files) for framework bindings. This should be fairly straightforward for CoreFoundation based libraries, but there are some challenges for Objective-C frameworks:

  1. Protocols (see #419 ). First iteration: ignore protocols.
  2. MyPy doesn't support metatypes (AFAIK). Current plan: don't include metaclasses in stub files (which also removes the need to come up with a way to represent those in stub files as Cocoa's regular and meta classes have the same name)
  3. Classes can have instance- and class- methods of the same name (such as +[NSObject description] and -[NSObject description]. Current plan: leave out class methods with the same name as an instance method.

Working on this will start after finishing #417. The result of this will likely be provisional for a number of releases, it will likely take a number of iterations to get this right.

glyph commented 1 year ago

3. Classes can have instance- and class- methods of the same name (such as +[NSObject description] and -[NSObject description]. Current plan: leave out class methods with the same name as an instance method.

I think you want to go the other way? Forgetting about Python's implementation semantics, if a stub calls something a class method, you can call it in either way:

class A:
    @classmethod
    def b(cls) -> int:
        return 7

class C:
    def d(self) -> int:
        return 8

A.b()
A().b()
C.d()
C().d()

In this example, the only error (rightfully so!) is from C.d().

If there are more esoteric cases where the signatures between class and instance method were to disagree somehow, you can even reflect that accurately (at both runtime and typecheck time!):

from __future__ import annotations
from typing import Callable, overload, reveal_type

class ClassOrInstance:
    @overload
    def __get__(self, instance: None, owner: type) -> Callable[[int], str]:
        ...

    @overload
    def __get__(self, instance: object, owner: type) -> Callable[[int], int]:
        ...

    def __get__(self, instance: object, owner: type) -> Callable[[int], str | int]:
        print(instance, owner)
        if instance is None:
            return lambda x: str(x)
        else:
            return lambda x: x

class Blub:
    method = ClassOrInstance()

reveal_type(Blub.method(3))
reveal_type(Blub().method(4))