Repository: python/mypy

Issue:
False positive: `Protocol member cannot be final` in non-instance assignment in a method
**Bug Report, reproduction, & actual behaviour**

See [mypy Playground](https://mypy-play.net/?mypy=latest&python=3.12&flags=strict&gist=653c1686ec0f7b04a9b8c054c31fe6f3):

```python
import typing as t

class A(t.Protocol):
    def method(self) -> None:
        var: t.Final = 0  # mypy: Protocol member cannot be final  [misc]
```

**Expected Behavior**

No issues

**Your Environment**

- Mypy version used: 1.10
- Mypy command-line flags: None
- Mypy configuration options from `mypy.ini` (and other config files): None
- Python version used: 3.10, 3.12


Discussion:
From my understanding, this behavior is a feature and not a bug. It is hard-coded and tested to ensure that protocol members cannot be marked as Final. Therefore, the expected behavior is correctly reported by mypy. Although this functionality can be restrictive in some use cases and maybe is worth considering a change. Should I try to make this modification?
This is a bug because `var` here is just a variable in the local function scope, not a protocol member. A protocol member would look like `self.var` inside the body of an instance method, or just `var` directly in the class body (not inside a method).
Thank you for the clarification

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -3505,7 +3505,8 @@ def unwrap_final(self, s: AssignmentStmt) -> bool:
         if self.loop_depth[-1] > 0:
             self.fail("Cannot use Final inside a loop", s)
         if self.type and self.type.is_protocol:
-            self.msg.protocol_members_cant_be_final(s)
+            if self.is_class_scope():
+                self.msg.protocol_members_cant_be_final(s)
         if (
             isinstance(s.rvalue, TempNode)
             and s.rvalue.no_rhs


Test patch:
diff --git a/test-data/unit/check-final.test b/test-data/unit/check-final.test
--- a/test-data/unit/check-final.test
+++ b/test-data/unit/check-final.test
@@ -301,6 +301,50 @@ class P(Protocol):
         pass
 [out]
 
+[case testFinalInProtocol]
+from typing import Final, Protocol, final
+
+class P(Protocol):
+    var1 : Final[int] = 0 # E: Protocol member cannot be final
+
+    @final # E: Protocol member cannot be final
+    def meth1(self) -> None:
+        var2: Final = 0
+
+    def meth2(self) -> None:
+        var3: Final = 0
+
+    def meth3(self) -> None:
+        class Inner:
+            var3: Final = 0  # OK
+
+            @final
+            def inner(self) -> None: ...
+
+    class Inner:
+        var3: Final = 0  # OK
+
+        @final
+        def inner(self) -> None: ...
+
+[out]
+
+[case testFinalWithClassVarInProtocol]
+from typing import Protocol, Final, final, ClassVar
+
+class P(Protocol):
+    var1 : Final[ClassVar[int]] = 0 # E: Variable should not be annotated with both ClassVar and Final
+    var2: ClassVar[int] = 1
+
+    @final # E: Protocol member cannot be final
+    def meth1(self) -> None:
+        ...
+
+    def meth2(self) -> None:
+        var3: Final[ClassVar[int]] = 0 # E: Variable should not be annotated with both ClassVar and Final # E: ClassVar can only be used for assignments in class body
+
+[out]
+
 [case testFinalNotInLoops]
 from typing import Final