Repository: python/mypy

Issue:
"'bool' is not a valid base class" should be possible to ignore.
<!--
  If you're new to mypy and you're not sure whether what you're experiencing is a mypy bug, please see the "Question and Help" form
  instead.
  Please also consider:

  - checking our common issues page: https://mypy.readthedocs.io/en/stable/common_issues.html
  - searching our issue tracker: https://github.com/python/mypy/issues to see if
    it's already been reported
  - asking on gitter chat: https://gitter.im/python/typing
-->

**Bug Report**

This code runs without error in Python, but mypy gives an error: 
```python
class Meta(type):
    def __new__(cls, name, bases, dct):
        return super().__new__(cls, name, (), dct)
    
class Foo(bool, metaclass=Meta):  # type: ignore
    ...

x = Foo()
```

The error is:
```python
error: "bool" is not a valid base class
Found 1 error in 1 file (errors prevented further checking)
```
Without the `type: ignore` it makes sense that an error is raised, but given that it's possible that it is valid code, it would be nice if it could be ignored.

Pylance seems to handle the above code without error.

**Use Case**
I came across this whilst looking into ORM code with 3.11's `dataclass_transform`. Example code is here:
https://mypy-play.net/?mypy=master&python=3.11&gist=763ad45ab009be9e0f7337347a299512

If there's a different way of getting the code shown to work I'd be interested to know, all I could think of was making `BoolField` a function instead of a class.

**Your Environment**

<!-- Include as many relevant details about the environment you experienced the bug in -->

- Mypy version used: main (mypy 0.980+dev.b8055512bd3cc54a0b82f8382ac39b3430cf9941.dirty (compiled: no))
- Mypy command-line flags: None
- Mypy configuration options from `mypy.ini` (and other config files): None
- Python version used: 3.10.1

Thanks for mypy :)




Discussion:
Looked into  implementing this and I see two main options:
1) Remove the check in the semantic analyser completely and let it fall back to the check in the type checker: `Cannot inherit from final class "bool"`
2) Make the check in the semantic analyser not a blocker, and make the type checker not raise the "final class" error for `bool`s (to prevent duplicate errors).

Assuming you'd be happy for this change, would either be preferable?


Thanks for bringing this up, I would merge a PR for option 1 (fall back to final check)

It looks like this was made a blocking check way back when in https://github.com/python/mypy/pull/1834. And the actual check goes back to 2013! Anyway, back then mypy had a lot more blocking checks and final did not exist.

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -1957,9 +1957,6 @@ def verify_base_classes(self, defn: ClassDef) -> bool:
             if self.is_base_class(info, baseinfo):
                 self.fail("Cycle in inheritance hierarchy", defn)
                 cycle = True
-            if baseinfo.fullname == "builtins.bool":
-                self.fail('"%s" is not a valid base class' % baseinfo.name, defn, blocker=True)
-                return False
         dup = find_duplicate(info.direct_base_classes())
         if dup:
             self.fail(f'Duplicate base class "{dup.name}"', defn, blocker=True)


Test patch:
diff --git a/mypy/test/testsemanal.py b/mypy/test/testsemanal.py
--- a/mypy/test/testsemanal.py
+++ b/mypy/test/testsemanal.py
@@ -132,7 +132,6 @@ def test_semanal_error(testcase: DataDrivenTestCase) -> None:
             alt_lib_path=test_temp_dir,
         )
         a = res.errors
-        assert a, f"No errors reported in {testcase.file}, line {testcase.line}"
     except CompileError as e:
         # Verify that there was a compile error and that the error messages
         # are equivalent.
diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test
--- a/test-data/unit/pythoneval.test
+++ b/test-data/unit/pythoneval.test
@@ -162,6 +162,12 @@ print(bool(''))
 True
 False
 
+[case testCannotExtendBoolUnlessIgnored]
+class A(bool): pass
+class B(bool): pass  # type: ignore
+[out]
+_program.py:1: error: Cannot inherit from final class "bool"
+
 [case testCallBuiltinTypeObjectsWithoutArguments]
 import typing
 print(int())
diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test
--- a/test-data/unit/semanal-errors.test
+++ b/test-data/unit/semanal-errors.test
@@ -775,7 +775,9 @@ class A(Generic[t]):
 [out]
 
 [case testTestExtendPrimitives]
-class C(bool): pass # E: "bool" is not a valid base class
+# Extending bool is not checked here as it should be typed
+# as final meaning the type checker will detect it.
+class C(bool): pass # ok
 class A(int): pass # ok
 class B(float): pass # ok
 class D(str): pass # ok