Added check for disallowed expression forms used with TypeAlias and with PEP 695 type statement. This addresses https://github.com/microsoft/pyright/issues/4320.

This commit is contained in:
Eric Traut 2022-12-11 19:45:17 -08:00
parent 44b1c3046c
commit 8e4ad79e97
7 changed files with 42 additions and 6 deletions

View File

@ -8,7 +8,7 @@
*/
import { getEmptyRange } from '../common/textRange';
import { NameNode, ParseNodeType } from '../parser/parseNodes';
import { ExpressionNode, NameNode, ParseNodeType } from '../parser/parseNodes';
import { AliasDeclaration, Declaration, DeclarationType, isAliasDeclaration, ModuleLoaderActions } from './declaration';
import { getFileInfoFromNode } from './parseTreeUtils';
@ -123,8 +123,11 @@ export function isPossibleTypeAliasDeclaration(decl: Declaration) {
// Perform a sanity check on the RHS expression. Some expression
// forms should never be considered legitimate for type aliases.
const rhsOfAssignment = decl.node.parent.rightExpression;
switch (rhsOfAssignment.nodeType) {
return isLegalTypeAliasExpressionForm(decl.node.parent.rightExpression);
}
export function isLegalTypeAliasExpressionForm(node: ExpressionNode) {
switch (node.nodeType) {
case ParseNodeType.Error:
case ParseNodeType.UnaryOperation:
case ParseNodeType.AssignmentExpression:

View File

@ -115,6 +115,7 @@ import {
getNameNodeForDeclaration,
isExplicitTypeAliasDeclaration,
isFinalVariableDeclaration,
isLegalTypeAliasExpressionForm,
isPossibleTypeAliasDeclaration,
} from './declarationUtils';
import {
@ -14779,6 +14780,15 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
if (isDeclaredTypeAlias(node.leftExpression)) {
typeAliasNameNode = (node.leftExpression as TypeAnnotationNode).valueExpression as NameNode;
if (!isLegalTypeAliasExpressionForm(node.rightExpression)) {
addDiagnostic(
fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.typeAliasIllegalExpressionForm(),
node.rightExpression
);
}
} else if (node.leftExpression.nodeType === ParseNodeType.Name) {
const symbolWithScope = lookUpSymbolRecursive(
node.leftExpression,
@ -15005,6 +15015,15 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
typeAliasTypeVar.details.recursiveTypeParameters = typeParameters;
}
if (!isLegalTypeAliasExpressionForm(node.expression)) {
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.typeAliasIllegalExpressionForm(),
node.expression
);
}
const aliasTypeResult = getTypeOfExpressionExpectingType(node.expression);
let isIncomplete = false;
let aliasType = aliasTypeResult.type;

View File

@ -786,6 +786,7 @@ export namespace Localizer {
export const tupleInAnnotation = () => getRawString('Diagnostic.tupleInAnnotation');
export const tupleIndexOutOfRange = () =>
new ParameterizedString<{ type: string; index: number }>(getRawString('Diagnostic.tupleIndexOutOfRange'));
export const typeAliasIllegalExpressionForm = () => getRawString('Diagnostic.typeAliasIllegalExpressionForm');
export const typeAliasIsRecursiveDirect = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.typeAliasIsRecursiveDirect'));
export const typeAliasIsRecursiveIndirect = () =>

View File

@ -390,6 +390,7 @@
"tupleAssignmentMismatch": "Expression with type \"{type}\" cannot be assigned to target tuple",
"tupleInAnnotation": "Tuple expression not allowed in type annotation",
"tupleIndexOutOfRange": "Index {index} is out of range for type {type}",
"typeAliasIllegalExpressionForm": "Invalid expression form for type alias definition",
"typeAliasIsRecursiveDirect": "Type alias \"{name}\" cannot use itself in its definition",
"typeAliasIsRecursiveIndirect": "Type alias \"{name}\" cannot refer to itself indirectly in its definition",
"typeAliasNotInModuleOrClass": "A TypeAlias can be defined only within a module or class scope",

View File

@ -34,3 +34,15 @@ if 1 < 2:
else:
type TA7 = int
def func1() -> type[int]:
...
# This should generate an error because a call expression is not
# allowed in a type alias definition.
type TA8 = func1()
# This should generate an error because a tuple and index expression is not
# allowed in a type alias definition.
type TA9 = (int, str, str)[0]

View File

@ -489,11 +489,11 @@ test('TypeAlias4', () => {
configOptions.defaultPythonVersion = PythonVersion.V3_9;
const analysisResults3_9 = TestUtils.typeAnalyzeSampleFiles(['typeAlias4.py'], configOptions);
TestUtils.validateResults(analysisResults3_9, 8);
TestUtils.validateResults(analysisResults3_9, 10);
configOptions.defaultPythonVersion = PythonVersion.V3_10;
const analysisResults3_10 = TestUtils.typeAnalyzeSampleFiles(['typeAlias4.py'], configOptions);
TestUtils.validateResults(analysisResults3_10, 7);
TestUtils.validateResults(analysisResults3_10, 9);
});
test('TypeAlias5', () => {

View File

@ -101,7 +101,7 @@ test('TypeAliasStatement1', () => {
configOptions.defaultPythonVersion = PythonVersion.V3_12;
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeAliasStatement1.py'], configOptions);
TestUtils.validateResults(analysisResults, 1);
TestUtils.validateResults(analysisResults, 3);
});
test('TypeAliasStatement2', () => {