mirror of
https://github.com/microsoft/pyright.git
synced 2024-11-05 02:46:22 +03:00
Added detection of errors when a namedtuple definition includes a language keyword. Also added minimal support for the rename
parameter to the namedtuple
function. This addresses #5423. (#5482)
Co-authored-by: Eric Traut <erictr@microsoft.com>
This commit is contained in:
parent
70ecdc2974
commit
e63f229d59
@ -18,26 +18,13 @@ import {
|
||||
ParseNodeType,
|
||||
StringListNode,
|
||||
} from '../parser/parseNodes';
|
||||
import { Tokenizer } from '../parser/tokenizer';
|
||||
import { getFileInfo } from './analyzerNodeInfo';
|
||||
import { DeclarationType, VariableDeclaration } from './declaration';
|
||||
import * as ParseTreeUtils from './parseTreeUtils';
|
||||
import { evaluateStaticBoolExpression } from './staticExpressions';
|
||||
import { Symbol, SymbolFlags } from './symbol';
|
||||
import { FunctionArgument, TypeEvaluator } from './typeEvaluatorTypes';
|
||||
import {
|
||||
AnyType,
|
||||
ClassType,
|
||||
ClassTypeFlags,
|
||||
combineTypes,
|
||||
FunctionParameter,
|
||||
FunctionType,
|
||||
FunctionTypeFlags,
|
||||
isClassInstance,
|
||||
isInstantiableClass,
|
||||
NoneType,
|
||||
TupleTypeArgument,
|
||||
Type,
|
||||
UnknownType,
|
||||
} from './types';
|
||||
import {
|
||||
computeMroLinearization,
|
||||
convertToInstance,
|
||||
@ -46,6 +33,21 @@ import {
|
||||
specializeTupleClass,
|
||||
synthesizeTypeVarForSelfCls,
|
||||
} from './typeUtils';
|
||||
import {
|
||||
AnyType,
|
||||
ClassType,
|
||||
ClassTypeFlags,
|
||||
FunctionParameter,
|
||||
FunctionType,
|
||||
FunctionTypeFlags,
|
||||
NoneType,
|
||||
TupleTypeArgument,
|
||||
Type,
|
||||
UnknownType,
|
||||
combineTypes,
|
||||
isClassInstance,
|
||||
isInstantiableClass,
|
||||
} from './types';
|
||||
|
||||
// Creates a new custom tuple factory class with named values.
|
||||
// Supports both typed and untyped variants.
|
||||
@ -59,6 +61,25 @@ export function createNamedTupleType(
|
||||
const fileInfo = getFileInfo(errorNode);
|
||||
let className = 'namedtuple';
|
||||
|
||||
// The "rename" parameter is supported only in the untyped version.
|
||||
let allowRename = false;
|
||||
if (!includesTypes) {
|
||||
const renameArg = argList.find(
|
||||
(arg) => arg.argumentCategory === ArgumentCategory.Simple && arg.name?.value === 'rename'
|
||||
);
|
||||
|
||||
if (renameArg?.valueExpression) {
|
||||
const renameValue = evaluateStaticBoolExpression(
|
||||
renameArg.valueExpression,
|
||||
fileInfo.executionEnvironment,
|
||||
fileInfo.definedConstants
|
||||
);
|
||||
if (renameValue === true) {
|
||||
allowRename = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (argList.length === 0) {
|
||||
evaluator.addError(Localizer.Diagnostic.namedTupleFirstArg(), errorNode);
|
||||
} else {
|
||||
@ -156,6 +177,14 @@ export function createNamedTupleType(
|
||||
entries.forEach((entryName, index) => {
|
||||
entryName = entryName.trim();
|
||||
if (entryName) {
|
||||
entryName = renameKeyword(
|
||||
evaluator,
|
||||
entryName,
|
||||
allowRename,
|
||||
entriesArg.valueExpression!,
|
||||
index
|
||||
);
|
||||
|
||||
const entryType = UnknownType.create();
|
||||
const paramInfo: FunctionParameter = {
|
||||
category: ParameterCategory.Simple,
|
||||
@ -232,6 +261,8 @@ export function createNamedTupleType(
|
||||
entryName = entryNameNode.strings.map((s) => s.value).join('');
|
||||
if (!entryName) {
|
||||
evaluator.addError(Localizer.Diagnostic.namedTupleEmptyName(), entryNameNode);
|
||||
} else {
|
||||
entryName = renameKeyword(evaluator, entryName, allowRename, entryNameNode, index);
|
||||
}
|
||||
} else {
|
||||
addGenericGetAttribute = true;
|
||||
@ -412,3 +443,27 @@ export function updateNamedTupleBaseClass(
|
||||
|
||||
return isUpdateNeeded;
|
||||
}
|
||||
|
||||
function renameKeyword(
|
||||
evaluator: TypeEvaluator,
|
||||
name: string,
|
||||
allowRename: boolean,
|
||||
errorNode: ExpressionNode,
|
||||
index: number
|
||||
): string {
|
||||
// Determine whether the name is a keyword in python.
|
||||
const isKeyword = Tokenizer.isKeyword(name);
|
||||
|
||||
if (!isKeyword) {
|
||||
// No rename necessary.
|
||||
return name;
|
||||
}
|
||||
|
||||
if (allowRename) {
|
||||
// Rename based on index.
|
||||
return `_${index}`;
|
||||
}
|
||||
|
||||
evaluator.addError(Localizer.Diagnostic.namedTupleNameKeyword(), errorNode);
|
||||
return name;
|
||||
}
|
||||
|
@ -583,6 +583,7 @@ export namespace Localizer {
|
||||
export const namedTupleEmptyName = () => getRawString('Diagnostic.namedTupleEmptyName');
|
||||
export const namedTupleFirstArg = () => getRawString('Diagnostic.namedTupleFirstArg');
|
||||
export const namedTupleMultipleInheritance = () => getRawString('Diagnostic.namedTupleMultipleInheritance');
|
||||
export const namedTupleNameKeyword = () => getRawString('Diagnostic.namedTupleNameKeyword');
|
||||
export const namedTupleNameType = () => getRawString('Diagnostic.namedTupleNameType');
|
||||
export const namedTupleNameUnique = () => getRawString('Diagnostic.namedTupleNameUnique');
|
||||
export const namedTupleNoTypes = () => getRawString('Diagnostic.namedTupleNoTypes');
|
||||
|
@ -268,6 +268,7 @@
|
||||
"namedTupleEmptyName": "Names within a named tuple cannot be empty",
|
||||
"namedTupleMultipleInheritance": "Multiple inheritance with NamedTuple is not supported",
|
||||
"namedTupleFirstArg": "Expected named tuple class name as first argument",
|
||||
"namedTupleNameKeyword": "Field names cannot be a keyword",
|
||||
"namedTupleNameType": "Expected two-entry tuple specifying entry name and type",
|
||||
"namedTupleNameUnique": "Names within a named tuple must be unique",
|
||||
"namedTupleNoTypes": "\"namedtuple\" provides no types for tuple entries; use \"NamedTuple\" instead",
|
||||
|
@ -91,6 +91,8 @@ const _keywords: Map<string, KeywordType> = new Map([
|
||||
['True', KeywordType.True],
|
||||
]);
|
||||
|
||||
const _softKeywords = new Set(['match', 'case', 'type']);
|
||||
|
||||
const _operatorInfo: { [key: number]: OperatorFlags } = {
|
||||
[OperatorType.Add]: OperatorFlags.Unary | OperatorFlags.Binary,
|
||||
[OperatorType.AddEqual]: OperatorFlags.Assignment,
|
||||
@ -364,6 +366,19 @@ export class Tokenizer {
|
||||
return _operatorInfo[operatorType];
|
||||
}
|
||||
|
||||
static isKeyword(name: string, includeSoftKeywords = false): boolean {
|
||||
const keyword = _keywords.get(name);
|
||||
if (!keyword) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (includeSoftKeywords) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !_softKeywords.has(name);
|
||||
}
|
||||
|
||||
static isOperatorAssignment(operatorType?: OperatorType): boolean {
|
||||
if (operatorType === undefined || _operatorInfo[operatorType] === undefined) {
|
||||
return false;
|
||||
|
24
packages/pyright-internal/src/tests/samples/namedTuple9.py
Normal file
24
packages/pyright-internal/src/tests/samples/namedTuple9.py
Normal file
@ -0,0 +1,24 @@
|
||||
# This sample tests the detection of keywords in a named tuple
|
||||
# definition and support for the "rename" parameter.
|
||||
|
||||
|
||||
from collections import namedtuple
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
# This should generate an error because "def" is a keyword.
|
||||
NT1 = namedtuple("NT1", ["abc", "def"])
|
||||
|
||||
# This should generate an error because "class" is a keyword.
|
||||
NT2 = namedtuple("NT2", ["abc", "class"], rename=False)
|
||||
|
||||
NT3 = namedtuple("NT3", ["abc", "def"], rename=True)
|
||||
|
||||
v3 = NT3(abc=0, _1=0)
|
||||
|
||||
# This should generate an error because "def" is a keyword.
|
||||
NT4 = NamedTuple("NT4", [("abc", int), ("def", int)])
|
||||
|
||||
|
||||
# These are soft keywords, so they shouldn't generate an error.
|
||||
NT5 = namedtuple("NT5", ["type", "match"])
|
@ -1374,6 +1374,12 @@ test('NamedTuple8', () => {
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
});
|
||||
|
||||
test('NamedTuple9', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['namedTuple9.py']);
|
||||
|
||||
TestUtils.validateResults(analysisResults, 3);
|
||||
});
|
||||
|
||||
test('Slots1', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['slots1.py']);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user