Added check for except* to enforce that exception class does not derive from BaseExceptionGroup.

This commit is contained in:
Eric Traut 2024-06-05 22:57:21 -07:00
parent 2ade792ba8
commit 16ea2a80c5
5 changed files with 74 additions and 17 deletions

View File

@ -1145,7 +1145,7 @@ export class Checker extends ParseTreeWalker {
const exceptionType = this._evaluator.getType(node.typeExpression);
if (exceptionType) {
this._validateExceptionType(exceptionType, node.typeExpression);
this._validateExceptionType(exceptionType, node.typeExpression, node.isExceptGroup);
}
}
@ -2943,7 +2943,9 @@ export class Checker extends ParseTreeWalker {
exceptionType: Type,
diag: DiagnosticAddendum,
baseExceptionType: Type | undefined,
allowTuple: boolean
baseExceptionGroupType: Type | undefined,
allowTuple: boolean,
isExceptGroup: boolean
) {
const derivesFromBaseException = (classType: ClassType) => {
if (!baseExceptionType || !isInstantiableClass(baseExceptionType)) {
@ -2953,6 +2955,14 @@ export class Checker extends ParseTreeWalker {
return derivesFromClassRecursive(classType, baseExceptionType, /* ignoreUnknown */ false);
};
const derivesFromBaseExceptionGroup = (classType: ClassType) => {
if (!baseExceptionGroupType || !isInstantiableClass(baseExceptionGroupType)) {
return true;
}
return derivesFromClassRecursive(classType, baseExceptionGroupType, /* ignoreUnknown */ false);
};
doForEachSubtype(exceptionType, (exceptionSubtype) => {
if (isAnyOrUnknown(exceptionSubtype)) {
return;
@ -2967,6 +2977,10 @@ export class Checker extends ParseTreeWalker {
})
);
}
if (isExceptGroup && derivesFromBaseExceptionGroup(exceptionSubtype)) {
diag.addMessage(LocMessage.exceptionGroupTypeIncorrect());
}
return;
}
@ -2976,7 +2990,9 @@ export class Checker extends ParseTreeWalker {
typeArg.type,
diag,
baseExceptionType,
/* allowTuple */ false
baseExceptionGroupType,
/* allowTuple */ false,
isExceptGroup
);
});
return;
@ -2991,11 +3007,19 @@ export class Checker extends ParseTreeWalker {
});
}
private _validateExceptionType(exceptionType: Type, errorNode: ExpressionNode): void {
private _validateExceptionType(exceptionType: Type, errorNode: ExpressionNode, isExceptGroup: boolean): void {
const baseExceptionType = this._evaluator.getBuiltInType(errorNode, 'BaseException');
const baseExceptionGroupType = this._evaluator.getBuiltInType(errorNode, 'BaseExceptionGroup');
const diagAddendum = new DiagnosticAddendum();
this._validateExceptionTypeRecursive(exceptionType, diagAddendum, baseExceptionType, /* allowTuple */ true);
this._validateExceptionTypeRecursive(
exceptionType,
diagAddendum,
baseExceptionType,
baseExceptionGroupType,
/* allowTuple */ true,
isExceptGroup
);
if (!diagAddendum.isEmpty()) {
this._evaluator.addDiagnostic(

View File

@ -442,6 +442,7 @@ export namespace Localizer {
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.enumMemberSet'));
export const enumMemberTypeAnnotation = () => getRawString('Diagnostic.enumMemberTypeAnnotation');
export const exceptionGroupIncompatible = () => getRawString('Diagnostic.exceptionGroupIncompatible');
export const exceptionGroupTypeIncorrect = () => getRawString('Diagnostic.exceptionGroupTypeIncorrect');
export const exceptionTypeIncorrect = () =>
new ParameterizedString<{ type: string }>(getRawString('Diagnostic.exceptionTypeIncorrect'));
export const exceptionTypeNotClass = () =>

View File

@ -145,6 +145,7 @@
"enumMemberSet": "Enum member \"{name}\" cannot be assigned",
"enumMemberTypeAnnotation": "Type annotations are not allowed for enum members",
"exceptionGroupIncompatible": "Exception group syntax (\"except*\") requires Python 3.11 or newer",
"exceptionGroupTypeIncorrect": "Exception type in except* cannot derive from BaseGroupException",
"exceptionTypeIncorrect": "\"{type}\" does not derive from BaseException",
"exceptionTypeNotClass": "\"{type}\" is not a valid exception class",
"exceptionTypeNotInstantiable": "Constructor for exception type \"{type}\" requires one or more arguments",

View File

@ -0,0 +1,31 @@
# This sample tests the syntax handling for Python 3.11 exception groups
# as described in PEP 654.
def func1():
try:
pass
# This should generate an error if using Python 3.10 or earlier.
except* ValueError as e:
reveal_type(e, expected_text="BaseExceptionGroup[ValueError]")
pass
# This should generate an error if using Python 3.10 or earlier.
except*:
pass
def func2():
try:
pass
# This should generate an error because ExceptionGroup derives
# from BaseExceptionGroup.
except* ExceptionGroup as e:
pass
# This should generate an error because ExceptionGroup derives
# from BaseExceptionGroup.
except* (ValueError, ExceptionGroup) as e:
pass

View File

@ -901,18 +901,6 @@ test('TryExcept6', () => {
TestUtils.validateResults(analysisResults, 1);
});
test('TryExcept7', () => {
const configOptions = new ConfigOptions(Uri.empty());
configOptions.defaultPythonVersion = pythonVersion3_10;
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['tryExcept7.py'], configOptions);
TestUtils.validateResults(analysisResults1, 3);
configOptions.defaultPythonVersion = pythonVersion3_11;
const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['tryExcept7.py'], configOptions);
TestUtils.validateResults(analysisResults2, 0);
});
test('TryExcept8', () => {
const configOptions = new ConfigOptions(Uri.empty());
@ -936,6 +924,18 @@ test('TryExcept11', () => {
TestUtils.validateResults(analysisResults, 0);
});
test('exceptionGroup1', () => {
const configOptions = new ConfigOptions(Uri.empty());
configOptions.defaultPythonVersion = pythonVersion3_10;
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['exceptionGroup1.py'], configOptions);
TestUtils.validateResults(analysisResults1, 9);
configOptions.defaultPythonVersion = pythonVersion3_11;
const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['exceptionGroup1.py'], configOptions);
TestUtils.validateResults(analysisResults2, 2);
});
test('Del1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['del1.py']);
TestUtils.validateResults(analysisResults, 6);