Modified diagnostic checks for list, dictionary, and tuple literal expressions within annotation expressions so they are emitted as part of the reportGeneralTypeIssue diagnostic rule rather than unconditionally. This addresses https://github.com/microsoft/pylance-release/issues/4587. (#5471)

Co-authored-by: Eric Traut <erictr@microsoft.com>
This commit is contained in:
Eric Traut 2023-07-12 00:10:49 +02:00 committed by GitHub
parent bc89399c9b
commit 72491ca691
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 76 additions and 39 deletions

View File

@ -1038,7 +1038,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
case ParseNodeType.List:
case ParseNodeType.Set: {
typeResult = getTypeOfListOrSet(node, inferenceContext);
typeResult = getTypeOfListOrSet(node, flags, inferenceContext);
break;
}
@ -1063,7 +1063,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
}
case ParseNodeType.Dictionary: {
typeResult = getTypeOfDictionary(node, inferenceContext);
typeResult = getTypeOfDictionary(node, flags, inferenceContext);
break;
}
@ -7304,6 +7304,23 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
flags: EvaluatorFlags,
inferenceContext: InferenceContext | undefined
): TypeResult {
if (
(flags & EvaluatorFlags.ExpectingTypeAnnotation) !== 0 &&
node.parent?.nodeType !== ParseNodeType.Argument
) {
// This is allowed inside of an index trailer, specifically
// to support Tuple[()], which is the documented way to annotate
// a zero-length tuple.
const diag = new DiagnosticAddendum();
diag.addMessage(Localizer.DiagnosticAddendum.useTupleInstead());
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.tupleInAnnotation() + diag.getString(),
node
);
}
if (
(flags & EvaluatorFlags.ExpectingInstantiableType) !== 0 &&
node.expressions.length === 0 &&
@ -7528,6 +7545,21 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
): TypeResult {
let baseTypeResult: TypeResult | undefined;
if (
(flags & EvaluatorFlags.ExpectingTypeAnnotation) !== 0 &&
node.leftExpression.nodeType === ParseNodeType.Name &&
node.leftExpression.value === 'type'
) {
const diag = new DiagnosticAddendum();
diag.addMessage(Localizer.DiagnosticAddendum.useTypeInstead());
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.typeCallNotAllowed() + diag.getString(),
node
);
}
// Handle immediate calls of lambdas specially.
if (node.leftExpression.nodeType === ParseNodeType.Lambda) {
baseTypeResult = getTypeOfLambdaForCall(node, inferenceContext);
@ -12251,7 +12283,25 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
return returnType;
}
function getTypeOfDictionary(node: DictionaryNode, inferenceContext: InferenceContext | undefined): TypeResult {
function getTypeOfDictionary(
node: DictionaryNode,
flags: EvaluatorFlags,
inferenceContext: InferenceContext | undefined
): TypeResult {
if (
(flags & EvaluatorFlags.ExpectingTypeAnnotation) !== 0 &&
node.parent?.nodeType !== ParseNodeType.Argument
) {
const diag = new DiagnosticAddendum();
diag.addMessage(Localizer.DiagnosticAddendum.useDictInstead());
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.dictInAnnotation() + diag.getString(),
node
);
}
// If the expected type is a union, analyze for each of the subtypes
// to find one that matches.
let effectiveExpectedType = inferenceContext?.expectedType;
@ -12730,7 +12780,26 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
return isIncomplete;
}
function getTypeOfListOrSet(node: ListNode | SetNode, inferenceContext: InferenceContext | undefined): TypeResult {
function getTypeOfListOrSet(
node: ListNode | SetNode,
flags: EvaluatorFlags,
inferenceContext: InferenceContext | undefined
): TypeResult {
if (
(flags & EvaluatorFlags.ExpectingTypeAnnotation) !== 0 &&
node.nodeType === ParseNodeType.List &&
node.parent?.nodeType !== ParseNodeType.Argument
) {
const diag = new DiagnosticAddendum();
diag.addMessage(Localizer.DiagnosticAddendum.useListInstead());
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.listInAnnotation() + diag.getString(),
node
);
}
// If the expected type is a union, recursively call for each of the subtypes
// to find one that matches.
let effectiveExpectedType = inferenceContext?.expectedType;

View File

@ -3436,14 +3436,6 @@ export class Parser {
this._isParsingTypeAnnotation = wasParsingTypeAnnotation;
if (this._isParsingTypeAnnotation) {
const diag = new DiagnosticAddendum();
if (atomExpression.nodeType === ParseNodeType.Name && atomExpression.value === 'type') {
diag.addMessage(Localizer.DiagnosticAddendum.useTypeInstead());
this._addError(Localizer.Diagnostic.typeCallNotAllowed() + diag.getString(), callNode);
}
}
atomExpression = callNode;
if (atomExpression.maxChildDepth !== undefined && atomExpression.maxChildDepth >= maxChildNodeDepth) {
@ -3818,18 +3810,6 @@ export class Parser {
if (nextToken.type === TokenType.OpenParenthesis) {
const possibleTupleNode = this._parseTupleAtom();
if (
possibleTupleNode.nodeType === ParseNodeType.Tuple &&
this._isParsingTypeAnnotation &&
!this._isParsingIndexTrailer
) {
// This is allowed inside of an index trailer, specifically
// to support Tuple[()], which is the documented way to annotate
// a zero-length tuple.
const diag = new DiagnosticAddendum();
diag.addMessage(Localizer.DiagnosticAddendum.useTupleInstead());
this._addError(Localizer.Diagnostic.tupleInAnnotation() + diag.getString(), possibleTupleNode);
}
if (
possibleTupleNode.nodeType === ParseNodeType.UnaryOperation ||
@ -3853,21 +3833,9 @@ export class Parser {
return possibleTupleNode;
} else if (nextToken.type === TokenType.OpenBracket) {
const listNode = this._parseListAtom();
if (this._isParsingTypeAnnotation && !this._isParsingIndexTrailer) {
const diag = new DiagnosticAddendum();
diag.addMessage(Localizer.DiagnosticAddendum.useListInstead());
this._addError(Localizer.Diagnostic.listInAnnotation() + diag.getString(), listNode);
}
return listNode;
return this._parseListAtom();
} else if (nextToken.type === TokenType.OpenCurlyBrace) {
const dictNode = this._parseDictionaryOrSetAtom();
if (this._isParsingTypeAnnotation && !this._isParsingIndexTrailer) {
const diag = new DiagnosticAddendum();
diag.addMessage(Localizer.DiagnosticAddendum.useDictInstead());
this._addError(Localizer.Diagnostic.dictInAnnotation() + diag.getString(), dictNode);
}
return dictNode;
return this._parseDictionaryOrSetAtom();
}
if (nextToken.type === TokenType.Keyword) {

View File

@ -910,7 +910,7 @@ test('ParamSpec19', () => {
test('ParamSpec20', () => {
const results = TestUtils.typeAnalyzeSampleFiles(['paramSpec20.py']);
TestUtils.validateResults(results, 6);
TestUtils.validateResults(results, 8);
});
test('ParamSpec21', () => {