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

More strict type checking #6

Open
asmeurer opened this issue Jan 24, 2024 · 4 comments
Open

More strict type checking #6

asmeurer opened this issue Jan 24, 2024 · 4 comments

Comments

@asmeurer
Copy link
Member

In numpy.array_api, we said that type checking that inputs were Array was too much overhead, and we would just rely on the the type signatures and type checking to do this.

However, given that we are no longer thinking of this library as something that is used in production, I don't think we need to worry so much about the overhead of type checking. It might be a good idea to add explicit type checks to functions. This would prevent a sufficiently duck-typed object from silently passing through, although that's pretty unlikely since basically every function uses x._array on its input. The real reason would be to provide better error messages that AttributeError on bad inputs.

Maybe this can be done automatically from the type signatures using one of those fancy libraries I know nothing about.

@rgommers
Copy link
Member

Sounds like a good idea.

Related: we may want to add a py.typed marker and a mypy CI job. When this code was inside numpy.array_api, it relied on numpy having a py.typed marker.

@asmeurer
Copy link
Member Author

These are the errors I get when I run mypy on array-api-strict:

array_api_strict/_creation_functions.py:94: error: Argument 1 to "arange" has incompatible type "int | float"; expected "bool | bool_ | int | integer[Any]"  [arg-type]
array_api_strict/_creation_functions.py:94: error: Argument "stop" to "arange" has incompatible type "int | float | None"; expected "bool | bool_ | int | integer[Any]"  [arg-type]
array_api_strict/_creation_functions.py:94: error: Argument "step" to "arange" has incompatible type "int | float"; expected "bool | bool_ | int | integer[Any]"  [arg-type]
array_api_strict/_creation_functions.py:94: error: Argument "dtype" to "arange" has incompatible type "_DType | None"; expected "None"  [arg-type]
array_api_strict/_creation_functions.py:115: error: Argument "dtype" to "empty" has incompatible type "_DType | None"; expected "None"  [arg-type]
array_api_strict/_creation_functions.py:133: error: Argument "dtype" to "empty_like" has incompatible type "_DType | None"; expected "None"  [arg-type]
array_api_strict/_creation_functions.py:157: error: Argument "dtype" to "eye" has incompatible type "_DType | None"; expected "None"  [arg-type]
array_api_strict/_creation_functions.py:163: error: Argument 1 to "from_dlpack" has incompatible type "object"; expected "_SupportsDLPack[None]"  [arg-type]
array_api_strict/_creation_functions.py:187: error: Argument "dtype" to "full" has incompatible type "_DType | None"; expected "None"  [arg-type]
array_api_strict/_creation_functions.py:215: error: Argument "dtype" to "full_like" has incompatible type "_DType | None"; expected "None"  [arg-type]
array_api_strict/_creation_functions.py:245: error: Argument "dtype" to "linspace" has incompatible type "_DType | None"; expected "None"  [arg-type]
array_api_strict/_creation_functions.py:264: error: Argument "indexing" to "meshgrid" has incompatible type "str"; expected "Literal['xy', 'ij']"  [arg-type]
array_api_strict/_creation_functions.py:286: error: Argument "dtype" to "ones" has incompatible type "_DType | None"; expected "None"  [arg-type]
array_api_strict/_creation_functions.py:304: error: Argument "dtype" to "ones_like" has incompatible type "_DType | None"; expected "None"  [arg-type]
array_api_strict/_creation_functions.py:353: error: Argument "dtype" to "zeros" has incompatible type "_DType | None"; expected "None"  [arg-type]
array_api_strict/_creation_functions.py:371: error: Argument "dtype" to "zeros_like" has incompatible type "_DType | None"; expected "None"  [arg-type]
array_api_strict/_array_object.py:162: error: Item "bool" of "bool | int | float | Array" has no attribute "dtype"  [union-attr]
array_api_strict/_array_object.py:162: error: Item "int" of "bool | int | float | Array" has no attribute "dtype"  [union-attr]
array_api_strict/_array_object.py:162: error: Item "float" of "bool | int | float | Array" has no attribute "dtype"  [union-attr]
array_api_strict/_array_object.py:174: error: Item "bool" of "bool | int | float | Array" has no attribute "dtype"  [union-attr]
array_api_strict/_array_object.py:174: error: Item "int" of "bool | int | float | Array" has no attribute "dtype"  [union-attr]
array_api_strict/_array_object.py:174: error: Item "float" of "bool | int | float | Array" has no attribute "dtype"  [union-attr]
array_api_strict/_array_object.py:177: error: Incompatible return value type (got "bool | int | float | Array", expected "Array")  [return-value]
array_api_strict/_array_object.py:507: error: Incompatible return value type (got "tuple[int, Literal[0]]", expected "tuple[IntEnum, int]")  [return-value]
array_api_strict/_array_object.py:509: error: Return type "Array" of "__eq__" incompatible with return type "bool" in supertype "object"  [override]
array_api_strict/_array_object.py:509: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object"  [override]
array_api_strict/_array_object.py:509: note: This violates the Liskov substitution principle
array_api_strict/_array_object.py:509: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
array_api_strict/_array_object.py:509: note: It is recommended for "__eq__" to work with arbitrary objects, for example:
array_api_strict/_array_object.py:509: note:     def __eq__(self, other: object) -> bool:
array_api_strict/_array_object.py:509: note:         if not isinstance(other, Array):
array_api_strict/_array_object.py:509: note:             return NotImplemented
array_api_strict/_array_object.py:509: note:         return <logic to compare two Array instances>
array_api_strict/_array_object.py:575: error: Incompatible types in assignment (expression has type "ndarray[Any, dtype[Any]]", variable has type "int | slice | ellipsis | tuple[int | slice | ellipsis | None, ...] | Array")  [assignment]
array_api_strict/_array_object.py:685: error: Return type "Array" of "__ne__" incompatible with return type "bool" in supertype "object"  [override]
array_api_strict/_array_object.py:685: error: Argument 1 of "__ne__" is incompatible with supertype "object"; supertype defines the argument type as "object"  [override]
array_api_strict/_array_object.py:685: note: This violates the Liskov substitution principle
array_api_strict/_array_object.py:685: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
array_api_strict/_array_object.py:765: error: Incompatible types in assignment (expression has type "ndarray[Any, dtype[Any]]", variable has type "int | slice | ellipsis | tuple[int | slice | ellipsis, ...] | Array")  [assignment]
array_api_strict/_array_object.py:1095: error: "Array" has no attribute "_dtype"; maybe "dtype"?  [attr-defined]
array_api_strict/_statistical_functions.py:70: error: Incompatible types in assignment (expression has type "type[floating[Any]]", variable has type "_DType | None")  [assignment]
array_api_strict/_statistical_functions.py:72: error: Incompatible types in assignment (expression has type "type[complexfloating[Any, Any]]", variable has type "_DType | None")  [assignment]
array_api_strict/_statistical_functions.py:75: error: Argument "dtype" to "prod" has incompatible type "_DType | None"; expected "None"  [arg-type]
array_api_strict/_statistical_functions.py:107: error: Incompatible types in assignment (expression has type "type[floating[Any]]", variable has type "_DType | None")  [assignment]
array_api_strict/_statistical_functions.py:109: error: Incompatible types in assignment (expression has type "type[complexfloating[Any, Any]]", variable has type "_DType | None")  [assignment]
array_api_strict/_statistical_functions.py:112: error: Argument "axis" to "sum" has incompatible type "int | tuple[int, ...] | None"; expected "None"  [arg-type]
array_api_strict/_statistical_functions.py:112: error: Argument "dtype" to "sum" has incompatible type "_DType | None"; expected "None"  [arg-type]
array_api_strict/_sorting_functions.py:23: error: Argument "kind" to "argsort" has incompatible type "str"; expected "Literal['quicksort', 'mergesort', 'heapsort', 'stable'] | None"  [arg-type]
array_api_strict/_sorting_functions.py:29: error: Argument "kind" to "argsort" has incompatible type "str"; expected "Literal['quicksort', 'mergesort', 'heapsort', 'stable'] | None"  [arg-type]
array_api_strict/_sorting_functions.py:51: error: No overload variant of "sort" matches argument types "ndarray[Any, dtype[Any]]", "int", "str"  [call-overload]
array_api_strict/_sorting_functions.py:51: note: Possible overload variants:
array_api_strict/_sorting_functions.py:51: note:     def [_SCT <: generic] sort(a: _SupportsArray[dtype[_SCT]] | _NestedSequence[_SupportsArray[dtype[_SCT]]], axis: SupportsIndex | None = ..., kind: Literal['quicksort', 'mergesort', 'heapsort', 'stable'] | None = ..., order: str | Sequence[str] | None = ...) -> ndarray[Any, dtype[_SCT]]
array_api_strict/_sorting_functions.py:51: note:     def sort(a: _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], axis: SupportsIndex | None = ..., kind: Literal['quicksort', 'mergesort', 'heapsort', 'stable'] | None = ..., order: str | Sequence[str] | None = ...) -> ndarray[Any, dtype[Any]]
array_api_strict/_indexing_functions.py:8: error: Name "Optional" is not defined  [name-defined]
array_api_strict/_indexing_functions.py:8: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Optional")
array_api_strict/_data_type_functions.py:113: error: Need type annotation for "fi"  [var-annotated]
array_api_strict/_data_type_functions.py:113: error: Argument 1 to "finfo" has incompatible type "_DType | Array"; expected "inexact[Never] | dtype[inexact[Never]] | type[inexact[Never]] | _SupportsDType[dtype[inexact[Never]]]"  [arg-type]
array_api_strict/_data_type_functions.py:122: error: Argument 6 to "finfo_object" has incompatible type "dtype[floating[Any]]"; expected "_DType"  [arg-type]
array_api_strict/_data_type_functions.py:134: error: Need type annotation for "ii"  [var-annotated]
array_api_strict/_data_type_functions.py:134: error: Argument 1 to "iinfo" has incompatible type "_DType | Array"; expected "dtype[Never] | type[Never] | _SupportsDType[dtype[Never]]"  [arg-type]
array_api_strict/_data_type_functions.py:135: error: Argument 4 to "iinfo_object" has incompatible type "dtype[Any]"; expected "_DType"  [arg-type]
array_api_strict/fft.py:69: error: Incompatible default for argument "s" (default has type "None", argument has type "Sequence[int]")  [assignment]
array_api_strict/fft.py:69: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
array_api_strict/fft.py:69: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
array_api_strict/fft.py:70: error: Incompatible default for argument "axes" (default has type "None", argument has type "Sequence[int]")  [assignment]
array_api_strict/fft.py:70: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
array_api_strict/fft.py:70: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
array_api_strict/fft.py:91: error: Incompatible default for argument "s" (default has type "None", argument has type "Sequence[int]")  [assignment]
array_api_strict/fft.py:91: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
array_api_strict/fft.py:91: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
array_api_strict/fft.py:92: error: Incompatible default for argument "axes" (default has type "None", argument has type "Sequence[int]")  [assignment]
array_api_strict/fft.py:92: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
array_api_strict/fft.py:92: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
array_api_strict/fft.py:157: error: Incompatible default for argument "s" (default has type "None", argument has type "Sequence[int]")  [assignment]
array_api_strict/fft.py:157: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
array_api_strict/fft.py:157: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
array_api_strict/fft.py:158: error: Incompatible default for argument "axes" (default has type "None", argument has type "Sequence[int]")  [assignment]
array_api_strict/fft.py:158: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
array_api_strict/fft.py:158: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
array_api_strict/fft.py:179: error: Incompatible default for argument "s" (default has type "None", argument has type "Sequence[int]")  [assignment]
array_api_strict/fft.py:179: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
array_api_strict/fft.py:179: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
array_api_strict/fft.py:180: error: Incompatible default for argument "axes" (default has type "None", argument has type "Sequence[int]")  [assignment]
array_api_strict/fft.py:180: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
array_api_strict/fft.py:180: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
array_api_strict/fft.py:261: error: Incompatible default for argument "axes" (default has type "None", argument has type "int | Sequence[int]")  [assignment]
array_api_strict/fft.py:261: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
array_api_strict/fft.py:261: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
array_api_strict/fft.py:271: error: Incompatible default for argument "axes" (default has type "None", argument has type "int | Sequence[int]")  [assignment]
array_api_strict/fft.py:271: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
array_api_strict/fft.py:271: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
array_api_strict/_manipulation_functions.py:22: error: Generator has incompatible item type "ndarray[Any, dtype[Any]]"; expected "Array"  [misc]
array_api_strict/_manipulation_functions.py:60: error: Name "Bool" is not defined  [name-defined]
array_api_strict/_manipulation_functions.py:111: error: Generator has incompatible item type "ndarray[Any, dtype[Any]]"; expected "Array"  [misc]
array_api_strict/linalg.py:17: error: Cannot find implementation or library stub for module named "numpy._core.numeric"  [import-not-found]
array_api_strict/linalg.py:17: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
array_api_strict/linalg.py:19: error: Module "numpy.core.numeric" has no attribute "normalize_axis_tuple"  [attr-defined]
array_api_strict/linalg.py:23: error: Module "array_api_strict._typing" has no attribute "Optional"  [attr-defined]
array_api_strict/linalg.py:23: error: Module "array_api_strict._typing" has no attribute "Tuple"  [attr-defined]
array_api_strict/linalg.py:309: error: Cannot find implementation or library stub for module named "numpy.linalg._linalg"  [import-not-found]
array_api_strict/linalg.py:472: error: "int" has no attribute "__iter__"; maybe "__int__"? (not iterable)  [attr-defined]
Found 67 errors in 9 files (checked 28 source files)

It looks like it's mostly issues with the numpy type hints. I'm not sure I understand where these type hints come from. It seems to think the arange arguments are typed as bool | bool_ | int | integer[Any], but arange is defined using overloads, which should include float: https://github.com/numpy/numpy/blob/261c4c0f34cea844dbb2f6aa86a7997b984efb09/numpy/_core/multiarray.pyi#L733.

@rgommers
Copy link
Member

I'm not sure I understand where these type hints come from.

They're shipped with numpy in .pyi files.

That looks like a long list of errors, nice to fix but not super urgent I'd say.

@asmeurer
Copy link
Member Author

Looks like the main runtime type checkers are typeguard and beartype. Both seem like they would require some degree of work to get working. Going to put this idea on the backburner for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants