Skip to content

Commit

Permalink
Added error reporting for instance and class variables within methods…
Browse files Browse the repository at this point in the history
… declared in a Protocol class. PEP 544 indicates that these should be flagged as an error.
  • Loading branch information
msfterictraut committed Feb 15, 2020
1 parent bc2c8d4 commit 7afbbca
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 1 deletion.
14 changes: 14 additions & 0 deletions server/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1631,6 +1631,14 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator {
assignTypeToMemberVariable(target, type, false, srcExpr);
}
}

// Assignments to instance or class variables through "self" or "cls" is not
// allowed for protocol classes.
if (ClassType.isProtocolClass(classTypeResults.classType)) {
addError(
`Assignment to instance or class variables not allowed within a Protocol class`,
target.memberName);
}
}
}
}
Expand Down Expand Up @@ -5866,6 +5874,12 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator {
}
}

if (fileInfo.executionEnvironment.pythonVersion >= PythonVersion.V38) {
if (ClassType.isBuiltIn(argType, 'Protocol')) {
classType.details.flags |= ClassTypeFlags.ProtocolClass;
}
}

// If the class directly derives from TypedDict or from a class that is
// a TypedDict, it is considered a TypedDict.
if (ClassType.isBuiltIn(argType, 'TypedDict') || ClassType.isTypedDictClass(argType)) {
Expand Down
9 changes: 8 additions & 1 deletion server/src/analyzer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,10 @@ export const enum ClassTypeFlags {

// The class is decorated with a "@final" decorator
// indicating that it cannot be subclassed.
Final = 1 << 10
Final = 1 << 10,

// The class derives directly from "Protocol".
ProtocolClass = 1 << 11
}

interface ClassDetails {
Expand Down Expand Up @@ -328,6 +331,10 @@ export namespace ClassType {
return !!(classType.details.flags & ClassTypeFlags.Final);
}

export function isProtocolClass(classType: ClassType) {
return !!(classType.details.flags & ClassTypeFlags.ProtocolClass);
}

export function getDataClassParameters(classType: ClassType): FunctionParameter[] {
return classType.details.dataClassParameters || [];
}
Expand Down
6 changes: 6 additions & 0 deletions server/src/tests/checker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,12 @@ test('Protocol3', () => {
validateResults(analysisResults, 1);
});

test('Protocol4', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['protocol4.py']);

validateResults(analysisResults, 2);
});

test('TypedDict1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typedDict1.py']);

Expand Down
16 changes: 16 additions & 0 deletions server/src/tests/samples/protocol4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# This sample tests that instance and class variables
# assigned within a Protocol method are flagged as errors.

from typing import List, Protocol

class Template(Protocol):
def method(self) -> None:
# This should be an error
self.temp: List[int] = []

@classmethod
def cls_method(cls) -> None:
# This should be an error
cls.test2 = 3


0 comments on commit 7afbbca

Please sign in to comment.