From ba3f0d4313dae034f0d6a6b57c772052b80662d3 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Tue, 14 Oct 2025 22:01:39 -0700 Subject: [PATCH] Fixed bug that results in a false positive when a `namedtuple` functional form is used with a field starting with an underscore and `rename=True`. This addresses #11033. (#11034) --- .../src/analyzer/namedTuples.ts | 62 +++++++++---------- .../src/tests/samples/namedTuple11.py | 3 + 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/packages/pyright-internal/src/analyzer/namedTuples.ts b/packages/pyright-internal/src/analyzer/namedTuples.ts index dd0c90b30..ea3dfcdb2 100644 --- a/packages/pyright-internal/src/analyzer/namedTuples.ts +++ b/packages/pyright-internal/src/analyzer/namedTuples.ts @@ -12,7 +12,7 @@ import { DiagnosticRule } from '../common/diagnosticRules'; import { convertOffsetsToRange } from '../common/positionUtils'; import { TextRange } from '../common/textRange'; import { LocMessage } from '../localization/localize'; -import { ArgCategory, ExpressionNode, ParamCategory, ParseNodeType, StringListNode } from '../parser/parseNodes'; +import { ArgCategory, ExpressionNode, ParamCategory, ParseNodeType } from '../parser/parseNodes'; import { Tokenizer } from '../parser/tokenizer'; import { getFileInfo } from './analyzerNodeInfo'; import { DeclarationType, VariableDeclaration } from './declaration'; @@ -170,6 +170,7 @@ export function createNamedTupleType( entriesArg.valueExpression && entriesArg.valueExpression.nodeType === ParseNodeType.StringList ) { + const entryNameNode = entriesArg.valueExpression; const entries = entriesArg.valueExpression.d.strings .map((s) => s.d.value) .join('') @@ -179,23 +180,8 @@ export function createNamedTupleType( entries.forEach((entryName, index) => { entryName = entryName.trim(); if (entryName) { - // Named tuples don't allow leading underscores in the field names. - if (entryName.startsWith('_')) { - evaluator.addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.namedTupleFieldUnderscore(), - entriesArg.valueExpression! - ); - return; - } - - entryName = renameKeyword( - evaluator, - entryName, - allowRename, - entriesArg.valueExpression!, - index - ); + entryName = renameUnderscore(evaluator, entryName, allowRename, entryNameNode, index); + entryName = renameKeyword(evaluator, entryName, allowRename, entryNameNode, index); const entryType = UnknownType.create(); const paramInfo = FunctionParam.create( @@ -214,15 +200,14 @@ export function createNamedTupleType( // In this case it's just part of a string literal value. // The definition provider won't necessarily take the // user to the exact spot in the string, but it's close enough. - const stringNode = entriesArg.valueExpression!; const declaration: VariableDeclaration = { type: DeclarationType.Variable, - node: stringNode as StringListNode, + node: entryNameNode, isRuntimeTypeExpression: true, uri: fileInfo.fileUri, range: convertOffsetsToRange( - stringNode.start, - TextRange.getEnd(stringNode), + entryNameNode.start, + TextRange.getEnd(entryNameNode), fileInfo.lines ), moduleName: fileInfo.moduleName, @@ -289,16 +274,7 @@ export function createNamedTupleType( entryNameNode ); } else { - // Named tuples don't allow leading underscores in the field names. - if (entryName.startsWith('_')) { - evaluator.addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.namedTupleFieldUnderscore(), - entryNameNode - ); - return; - } - + entryName = renameUnderscore(evaluator, entryName, allowRename, entryNameNode, index); entryName = renameKeyword(evaluator, entryName, allowRename, entryNameNode, index); } } else { @@ -513,3 +489,25 @@ function renameKeyword( evaluator.addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.namedTupleNameKeyword(), errorNode); return name; } + +function renameUnderscore( + evaluator: TypeEvaluator, + name: string, + allowRename: boolean, + errorNode: ExpressionNode, + index: number +): string { + if (!name.startsWith('_')) { + // No rename necessary. + return name; + } + + if (allowRename) { + // Rename based on index. + return `_${index}`; + } + + evaluator.addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.namedTupleFieldUnderscore(), errorNode); + + return name; +} diff --git a/packages/pyright-internal/src/tests/samples/namedTuple11.py b/packages/pyright-internal/src/tests/samples/namedTuple11.py index f4b45758c..1a5faa34e 100644 --- a/packages/pyright-internal/src/tests/samples/namedTuple11.py +++ b/packages/pyright-internal/src/tests/samples/namedTuple11.py @@ -17,3 +17,6 @@ class NT3(NamedTuple): # This should generate an error because a field name starting with an # underscore isn't allowed. _oops: int + + +NT4 = namedtuple("NT4", "a, b, _c", rename=True)