Added check for an attempt to overwrite a method that is marked @final.

This commit is contained in:
Eric Traut 2022-05-09 21:31:23 -07:00
parent 051f74cd68
commit 07892e0da7
3 changed files with 37 additions and 3 deletions

View File

@ -5309,6 +5309,30 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
}
}
} else if (isFunction(concreteSubtype) || isOverloadedFunction(concreteSubtype)) {
// Check for an attempt to overwrite a final method.
if (usage.method === 'set') {
let isFinal = false;
if (isFunction(concreteSubtype)) {
isFinal = FunctionType.isFinal(concreteSubtype);
} else {
const impl = concreteSubtype.overloads.find((f) => !FunctionType.isOverloaded(f));
if (impl) {
isFinal = FunctionType.isFinal(impl);
}
}
if (isFinal && memberInfo && isClass(memberInfo.classType)) {
diag?.addMessage(
Localizer.Diagnostic.finalMethodOverride().format({
name: memberName,
className: memberInfo.classType.details.name,
})
);
isTypeValid = false;
return undefined;
}
}
// If this function is an instance member (e.g. a lambda that was
// assigned to an instance variable), don't perform any binding.
if (!isAccessedThroughObject || (memberInfo && !memberInfo.isInstanceMember)) {
@ -5334,11 +5358,11 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
}
// Check for an attempt to overwrite a final member variable.
const finalTypeDecl = memberInfo?.symbol
const finalVarTypeDecl = memberInfo?.symbol
.getDeclarations()
.find((decl) => isFinalVariableDeclaration(decl));
if (finalTypeDecl && !ParseTreeUtils.isNodeContainedWithin(errorNode, finalTypeDecl.node)) {
if (finalVarTypeDecl && !ParseTreeUtils.isNodeContainedWithin(errorNode, finalVarTypeDecl.node)) {
// If a Final instance variable is declared in the class body but is
// being assigned within an __init__ method, it's allowed.
const enclosingFunctionNode = ParseTreeUtils.getEnclosingFunction(errorNode);

View File

@ -29,6 +29,16 @@ class ClassA:
pass
# This should generate an error because func3 is final.
ClassA.func3 = lambda self: None
# This should generate an error because func4 is final.
ClassA.func4 = lambda cls: None
# This should generate an error because _func5 is final.
ClassA._func5 = lambda self: None
class ClassB(ClassA):
def func1(self):
pass

View File

@ -297,7 +297,7 @@ test('Final1', () => {
test('Final2', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['final2.py']);
TestUtils.validateResults(analysisResults, 4);
TestUtils.validateResults(analysisResults, 7);
});
test('Final3', () => {