Repository: numpy/numpy
File: numpy/_core/tests/test_scalar_methods.py (Python)

Code before:
    def test_abc_complexfloating(self) -> None:
        alias = np.complexfloating[Any, Any]
        assert isinstance(alias, types.GenericAlias)
        assert alias.__origin__ is np.complexfloating

    @pytest.mark.parametrize("arg_len", range(4))
    def test_abc_complexfloating_subscript_tuple(self, arg_len: int) -> None:
        arg_tup = (Any,) * arg_len
        if arg_len in (1, 2):
            assert np.complexfloating[arg_tup]
        else:
            match = f"Too {'few' if arg_len == 0 else 'many'} arguments"
            with pytest.raises(TypeError, match=match):
                np.complexfloating[arg_tup]

    @pytest.mark.parametrize("cls", [np.generic, np.flexible, np.character])
    def test_abc_non_numeric(self, cls: type[np.generic]) -> None:
        with pytest.raises(TypeError):
            cls[Any]

    @pytest.mark.parametrize("code", np.typecodes["All"])
    def test_concrete(self, code: str) -> None:
        cls = np.dtype(code).type
        if cls == np.bool:
            # np.bool allows subscript
            assert cls[Any]
        else:
            with pytest.raises(TypeError):
                cls[Any]

    @pytest.mark.parametrize("arg_len", range(4))
    def test_subscript_tuple(self, arg_len: int) -> None:
        arg_tup = (Any,) * arg_len
        if arg_len == 1:
            assert np.number[arg_tup]
        else:
            with pytest.raises(TypeError):
                np.number[arg_tup]

    def test_subscript_scalar(self) -> None:
        assert np.number[Any]


class TestBitCount:
    # derived in part from the cpython test "test_bit_count"

    @pytest.mark.parametrize("itype", sctypes['int'] + sctypes['uint'])
    def test_small(self, itype):
        for a in range(max(np.iinfo(itype).min, 0), 128):
            msg = f"Smoke test for {itype}({a}).bit_count()"
            assert itype(a).bit_count() == a.bit_count(), msg

Review comment (bug):
I guess this one has been sitting for a month or so, perhaps because it is a bit esoteric. I checked locally that the regression test fails before the source patch is applied.

I also verified that `assert_type(cls, np.dtype[npt.NDArray[bool]])` works on this branch, as does the original `assert_type(cls, np.dtype[np.bool[bool]])`. I find the latter a little harder to parse, perhaps because it appears to be information-redundant, but I mostly gave up on understanding the static typing details beyond the surface level.

Anyway, I guess this one is for @jorenham to check? Would it make sense to have the explicit `assert_type` check somewhere as well, rather than just the absence of error here? I'm pretty sure I've seen some test files with `assert_type` all over the place--not sure if those would run the runtime checks or just the "type checking" time checks, but Joren will know.

I'm not sure I really want to know the answer, but is there an intuitive explanation for why only the `np.bool` type allows subscripting, and not the others?

The runtime type check seems a bit confusing though, because the diff below the fold also appears to pass on this branch:

<details>

```diff
--- a/numpy/_core/tests/test_scalar_methods.py
+++ b/numpy/_core/tests/test_scalar_methods.py
@@ -4,7 +4,7 @@
 import fractions
 import platform
 import types
-from typing import Any
+from typing import Any, assert_type
 
 import pytest
 
@@ -171,12 +171,7 @@ def test_abc_non_numeric(self, cls: type[np.generic]) -> None:
     @pytest.mark.parametrize("code", np.typecodes["All"])
     def test_concrete(self, code: str) -> None:
         cls = np.dtype(code).type
-        if cls == np.bool:
-            # np.bool allows subscript
-            assert cls[Any]
-        else:
-            with pytest.raises(TypeError):
-                cls[Any]
+        assert_type(cls, np.dtype[np.bool[bool]])
```

</details>

The `reveal_type` seems pretty crude for `cls` in general across the types at runtime here, which kind of makes me wonder how much value one really gets out of the runtime check in the original ticket--I suppose not much, and the only real argument was that it shouldn't error out.

Code after:
    def test_abc_complexfloating(self) -> None:
        alias = np.complexfloating[Any, Any]
        assert isinstance(alias, types.GenericAlias)
        assert alias.__origin__ is np.complexfloating

    @pytest.mark.parametrize("arg_len", range(4))
    def test_abc_complexfloating_subscript_tuple(self, arg_len: int) -> None:
        arg_tup = (Any,) * arg_len
        if arg_len in (1, 2):
            assert np.complexfloating[arg_tup]
        else:
            match = f"Too {'few' if arg_len == 0 else 'many'} arguments"
            with pytest.raises(TypeError, match=match):
                np.complexfloating[arg_tup]

    @pytest.mark.parametrize("cls", [np.generic, np.flexible, np.character])
    def test_abc_non_numeric(self, cls: type[np.generic]) -> None:
        with pytest.raises(TypeError):
            cls[Any]

    @pytest.mark.parametrize("code", np.typecodes["All"])
    def test_concrete(self, code: str) -> None:
        cls = np.dtype(code).type
        if cls == np.bool:
            # np.bool allows subscript
            assert cls[Any]
        else:
            with pytest.raises(TypeError):
                cls[Any]

    @pytest.mark.parametrize("arg_len", range(4))
    def test_subscript_tuple(self, arg_len: int) -> None:
        arg_tup = (Any,) * arg_len
        if arg_len == 1:
            assert np.number[arg_tup]
        else:
            with pytest.raises(TypeError):
                np.number[arg_tup]

    def test_subscript_scalar(self) -> None:
        assert np.number[Any]

    @pytest.mark.parametrize("subscript", [Literal[True], Literal[False]])
    def test_subscript_bool(self, subscript: Literal[True, False]) -> None:
        assert isinstance(np.bool[subscript], types.GenericAlias)


class TestBitCount:
    # derived in part from the cpython test "test_bit_count"

    @pytest.mark.parametrize("itype", sctypes['int'] + sctypes['uint'])