Added a check for class pattern matches where the number of positional patterns exceeds the number of positional fields supported by the class. This addresses #6019.

This commit is contained in:
Eric Traut 2023-09-25 09:31:57 -07:00
parent aa67d94adb
commit 0e95e48650
19 changed files with 108 additions and 25 deletions

View File

@ -1669,16 +1669,9 @@ export function validateClassPattern(evaluator: TypeEvaluator, pattern: PatternC
} else {
const isBuiltIn = isClassSpecialCaseForClassPattern(exprType);
// If it's a special-case builtin class, only one positional argument is allowed.
// If it's a special-case builtin class, only positional arguments are allowed.
if (isBuiltIn) {
if (pattern.arguments.length > 1) {
evaluator.addDiagnostic(
getFileInfo(pattern).diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.classPatternBuiltInArgCount(),
pattern.arguments[1]
);
} else if (pattern.arguments.length === 1 && pattern.arguments[0].name) {
if (pattern.arguments.length === 1 && pattern.arguments[0].name) {
evaluator.addDiagnostic(
getFileInfo(pattern).diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
@ -1687,6 +1680,36 @@ export function validateClassPattern(evaluator: TypeEvaluator, pattern: PatternC
);
}
}
// Emits an error if the supplied number of positional patterns is less than
// expected for the given subject type.
let positionalPatternCount = pattern.arguments.findIndex((arg) => arg.name !== undefined);
if (positionalPatternCount < 0) {
positionalPatternCount = pattern.arguments.length;
}
let expectedPatternCount = 1;
if (!isBuiltIn) {
let positionalArgNames: string[] = [];
if (pattern.arguments.some((arg) => !arg.name)) {
positionalArgNames = getPositionalMatchArgNames(evaluator, exprType);
}
expectedPatternCount = positionalArgNames.length;
}
if (positionalPatternCount > expectedPatternCount) {
evaluator.addDiagnostic(
getFileInfo(pattern).diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.classPatternPositionalArgCount().format({
type: exprType.details.name,
expected: expectedPatternCount,
received: positionalPatternCount,
}),
pattern.arguments[expectedPatternCount]
);
}
}
}

View File

@ -281,9 +281,12 @@ export namespace Localizer {
export const classMethodClsParam = () => getRawString('Diagnostic.classMethodClsParam');
export const classNotRuntimeSubscriptable = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.classNotRuntimeSubscriptable'));
export const classPatternBuiltInArgCount = () => getRawString('Diagnostic.classPatternBuiltInArgCount');
export const classPatternBuiltInArgPositional = () =>
getRawString('Diagnostic.classPatternBuiltInArgPositional');
export const classPatternPositionalArgCount = () =>
new ParameterizedString<{ type: string; expected: number; received: number }>(
getRawString('Diagnostic.classPatternPositionalArgCount')
);
export const classPatternTypeAlias = () =>
new ParameterizedString<{ type: string }>(getRawString('Diagnostic.classPatternTypeAlias'));
export const classTypeParametersIllegal = () => getRawString('Diagnostic.classTypeParametersIllegal');

View File

@ -67,7 +67,6 @@
"classGetItemClsParam": "Přepsání __class_getitem__ by mělo mít parametr cls",
"classMethodClsParam": "Metody třídy by měly mít parametr „cls“",
"classNotRuntimeSubscriptable": "Dolní index pro třídu {name} vygeneruje výjimku modulu runtime; anotaci typu uzavřete do uvozovek",
"classPatternBuiltInArgCount": "Vzor třídy přijímá maximálně 1 poziční dílčí vzor",
"classPatternBuiltInArgPositional": "Vzor třídy přijímá pouze poziční dílčí vzor",
"classPatternTypeAlias": "Typ „{type}“ nelze použít ve vzorci třídy, protože se jedná o specializovaný alias typu",
"classTypeParametersIllegal": "Syntaxe parametru typu třídy vyžaduje Python 312 nebo novější",

View File

@ -67,7 +67,6 @@
"classGetItemClsParam": "__class_getitem__ Außerkraftsetzung sollte einen \"cls\"-Parameter annehmen.",
"classMethodClsParam": "Klassenmethoden sollten einen \"cls\"-Parameter verwenden.",
"classNotRuntimeSubscriptable": "Durch das Tiefstellungsskript für die Klasse \"{name}\" wird eine Laufzeitausnahme generiert; schließen Sie die Typanmerkung in Anführungszeichen ein",
"classPatternBuiltInArgCount": "Das Klassenmuster akzeptiert höchstens 1 positionsbezogenes Untermuster.",
"classPatternBuiltInArgPositional": "Das Klassenmuster akzeptiert nur positionsbezogenes Untermuster.",
"classPatternTypeAlias": "\"{type}\" kann nicht in einem Klassenmuster verwendet werden, da es sich um einen spezialisierten Typalias handelt.",
"classTypeParametersIllegal": "Die Syntax des Klassentypparameters erfordert Python 3.12 oder höher.",

View File

@ -52,8 +52,8 @@
"classGetItemClsParam": "__class_getitem__ override should take a \"cls\" parameter",
"classMethodClsParam": "Class methods should take a \"cls\" parameter",
"classNotRuntimeSubscriptable": "Subscript for class \"{name}\" will generate runtime exception; enclose type annotation in quotes",
"classPatternBuiltInArgCount": "Class pattern accepts at most 1 positional sub-pattern",
"classPatternBuiltInArgPositional": "Class pattern accepts only positional sub-pattern",
"classPatternPositionalArgCount": "Too many positional patterns for class \"{type}\"; expected {expected} but received {received}",
"classPatternTypeAlias": "\"{type}\" cannot be used in a class pattern because it is a specialized type alias",
"classTypeParametersIllegal": "Class type parameter syntax requires Python 3.12 or newer",
"classVarNotAllowed": "\"ClassVar\" is not allowed in this context",

View File

@ -67,7 +67,6 @@
"classGetItemClsParam": "__class_getitem__ debe tomar un parámetro \"cls\"",
"classMethodClsParam": "Los métodos de clase deben tomar un parámetro \"cls\"",
"classNotRuntimeSubscriptable": "El subíndice para la clase \"{name}\" generará una excepción en tiempo de ejecución; encierre la anotación de tipo entre comillas",
"classPatternBuiltInArgCount": "El patrón de clase acepta como máximo 1 subpatrón posicional",
"classPatternBuiltInArgPositional": "El patrón de clase solo acepta subpatrones posicionales",
"classPatternTypeAlias": "\"{type}\" no se puede usar en un patrón de clase porque es un alias de tipo especializado",
"classTypeParametersIllegal": "La sintaxis de los parámetros de tipo de clase requiere Python 3.12 o posterior.",

View File

@ -67,7 +67,6 @@
"classGetItemClsParam": "__class_getitem__ remplacement doit prendre un paramètre « cls »",
"classMethodClsParam": "Les méthodes de classe doivent prendre un paramètre \"cls\"",
"classNotRuntimeSubscriptable": "Lindice de la classe « {name} » génère une exception dexécution ; placer lannotation de type entre guillemets",
"classPatternBuiltInArgCount": "Le modèle de classe accepte au maximum 1 sous-modèle positionnel",
"classPatternBuiltInArgPositional": "Le modèle de classe accepte uniquement le sous-modèle positionnel",
"classPatternTypeAlias": "\"{type}\" ne peut pas être utilisé dans un modèle de classe car il s'agit d'un alias de type spécialisé",
"classTypeParametersIllegal": "La syntaxe du paramètre de type de classe nécessite Python 3.12 ou version ultérieure",

View File

@ -67,7 +67,6 @@
"classGetItemClsParam": "__class_getitem__ override deve accettare un parametro \"cls\"",
"classMethodClsParam": "I metodi di classe devono accettare un parametro \"cls\"",
"classNotRuntimeSubscriptable": "Il pedice per la classe \"{name}\" genererà un'eccezione di runtime; racchiudere l'annotazione di tipo tra virgolette",
"classPatternBuiltInArgCount": "Il modello di classe accetta al massimo 1 sotto pattern posizionale",
"classPatternBuiltInArgPositional": "Il modello di classe accetta solo un sotto pattern posizionale",
"classPatternTypeAlias": "\"{type}\" non può essere usato in uno schema di classe, perché è un alias di tipo specializzato",
"classTypeParametersIllegal": "La sintassi del parametro del tipo di classe richiede Python 3.12 o versione successiva",

View File

@ -67,7 +67,6 @@
"classGetItemClsParam": "__class_getitem__ override は \"cls\" パラメーターを受け取る必要があります",
"classMethodClsParam": "クラス メソッドは \"cls\" パラメーターを受け取る必要があります",
"classNotRuntimeSubscriptable": "クラス \"{name}\" の添字はランタイム例外を生成します。型の注釈を引用符で囲む",
"classPatternBuiltInArgCount": "クラス パターンは、最大 1 つの位置指定サブパターンを受け入れます",
"classPatternBuiltInArgPositional": "クラス パターンは位置指定サブパターンのみを受け入れます",
"classPatternTypeAlias": "\"{type}\" は特殊な型エイリアスであるため、クラス パターンでは使用できません",
"classTypeParametersIllegal": "クラス型パラメーターの構文には Python 3.12 以降が必要です",

View File

@ -67,7 +67,6 @@
"classGetItemClsParam": "__class_getitem__ 재정의는 \"cls\" 매개 변수를 사용해야 합니다.",
"classMethodClsParam": "클래스 메서드는 cls 매개 변수를 사용해야 합니다.",
"classNotRuntimeSubscriptable": "클래스 \"{name}\"에 대한 첨자는 런타임 예외를 생성합니다. 따옴표로 형식 주석 묶기",
"classPatternBuiltInArgCount": "클래스 패턴은 최대 1개의 위치 하위 패턴을 허용합니다.",
"classPatternBuiltInArgPositional": "클래스 패턴은 위치 하위 패턴만 허용합니다.",
"classPatternTypeAlias": "{type}’은(는) 특수 형식 별칭이므로 클래스 패턴에서 사용할 수 없습니다.",
"classTypeParametersIllegal": "클래스 형식 매개 변수 구문에는 Python 3.12 이상이 필요합니다.",

View File

@ -67,7 +67,6 @@
"classGetItemClsParam": "Przesłonięcie __class_getitem__ powinno przyjmować parametr „cls”.",
"classMethodClsParam": "Metody klasy powinny przyjmować parametr „cls”",
"classNotRuntimeSubscriptable": "Indeks dolny dla klasy „{name}” wygeneruje wyjątek czasu uruchamiania; umieść adnotację typu w cudzysłowie",
"classPatternBuiltInArgCount": "Wzorzec klasy akceptuje co najwyżej 1 podwzorzec pozycyjny",
"classPatternBuiltInArgPositional": "Wzorzec klasy akceptuje tylko podwzorzec pozycyjny",
"classPatternTypeAlias": "„{type}” nie może być używany we wzorcu klasy, ponieważ jest to alias typu specjalnego",
"classTypeParametersIllegal": "Składnia parametru typu klasy wymaga języka Python w wersji 3.12 lub nowszej",

View File

@ -67,7 +67,6 @@
"classGetItemClsParam": "A substituição__class_getitem__ deve usar um parâmetro \"cls\"",
"classMethodClsParam": "Os métodos de classe devem usar um parâmetro \"cls\"",
"classNotRuntimeSubscriptable": "O subscrito para a classe \"{name}\" gerará uma exceção de runtime. Coloque a anotação de tipo entre aspas",
"classPatternBuiltInArgCount": "O padrão de classe aceita no máximo 1 sub-padrão posicional",
"classPatternBuiltInArgPositional": "O padrão de classe aceita apenas sub-padrão posicional",
"classPatternTypeAlias": "\"{type}\" não pode ser usado em um padrão de classe porque é um alias de tipo especializado",
"classTypeParametersIllegal": "A sintaxe do parâmetro de tipo de classe requer o Python 3.12 ou mais recente",

View File

@ -67,7 +67,6 @@
"classGetItemClsParam": "[A2iHF][นั้__çlæss_gëtïtëm__ øvërrïðë shøµlð tækë æ \"çls\" pæræmëtërẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्นั้ढूँ]",
"classMethodClsParam": "[aWMN3][นั้Çlæss mëthøðs shøµlð tækë æ \"çls\" pæræmëtërẤğ倪İЂҰक्र्तिृまẤğ倪นั้ढूँ]",
"classNotRuntimeSubscriptable": "[O9BL6][นั้§µþsçrïpt før çlæss \"{ñæmë}\" wïll gëñërætë rµñtïmë ëxçëptïøñ; ëñçløsë tÿpë æññøtætïøñ ïñ qµøtësẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्นั้ढूँ]",
"classPatternBuiltInArgCount": "[moI4V][นั้Çlæss pættërñ æççëpts æt møst 1 pøsïtïøñæl sµþ-pættërñẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्นั้ढूँ]",
"classPatternBuiltInArgPositional": "[DOfs5][นั้Çlæss pættërñ æççëpts øñlÿ pøsïtïøñæl sµþ-pættërñẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰนั้ढूँ]",
"classPatternTypeAlias": "[AxDtv][นั้\"{tÿpë}\" çæññøt þë µsëð ïñ æ çlæss pættërñ þëçæµsë ït ïs æ spëçïælïzëð tÿpë ælïæsẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृまẤğ倪İนั้ढूँ]",
"classTypeParametersIllegal": "[GybXD][นั้Çlæss tÿpë pæræmëtër sÿñtæx rëqµïrës Pÿthøñ 3.12 ør ñëwërẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्นั้ढूँ]",

View File

@ -67,7 +67,6 @@
"classGetItemClsParam": "Переопределение метода __class_getitem__ должно принимать параметр \"cls\"",
"classMethodClsParam": "Методы класса должны принимать параметр cls",
"classNotRuntimeSubscriptable": "Операция взятия подстроки для класса \"{name}\" создаст исключение среды выполнения; заключите заметку типа в кавычки",
"classPatternBuiltInArgCount": "Шаблон класса принимает не более одного позиционного вложенного шаблона",
"classPatternBuiltInArgPositional": "Шаблон класса принимает только позиционный вложенный шаблон",
"classPatternTypeAlias": "\"{type}\" нельзя использовать в шаблоне класса, поскольку это псевдоним специализированного типа",
"classTypeParametersIllegal": "Синтаксис параметра типа класса может использоваться в Python версии не ниже 3.12.",

View File

@ -67,7 +67,6 @@
"classGetItemClsParam": "__class_getitem__ geçersiz kılması bir \"cls\" parametresi almalı",
"classMethodClsParam": "Sınıf metotları bir \"cls\" parametresi almalıdır",
"classNotRuntimeSubscriptable": "\"{name}\" sınıfına ait alt simge çalışma zamanı özel durumunu oluşturur; tür ek açıklamalarını tırnak içine alın",
"classPatternBuiltInArgCount": "Sınıf deseni en fazla 1 konumsal alt deseni kabul eder",
"classPatternBuiltInArgPositional": "Sınıf deseni yalnızca konumsal alt desen kabul eder",
"classPatternTypeAlias": "\"{type}\" özel bir tür diğer adı olduğundan sınıf deseninde kullanılamaz",
"classTypeParametersIllegal": "Sınıf türü parametresi söz dizimi için Python 3.12 veya daha yeni bir sürümü gerekiyor",

View File

@ -67,7 +67,6 @@
"classGetItemClsParam": "__class_getitem__替代应采用“cls”参数",
"classMethodClsParam": "类方法应采用“cls”参数",
"classNotRuntimeSubscriptable": "类“{name}”的下标将生成运行时异常;将类型批注括在引号中",
"classPatternBuiltInArgCount": "类模式最多接受 1 个位置子模式",
"classPatternBuiltInArgPositional": "类模式仅接受位置子模式",
"classPatternTypeAlias": "无法在类模式中使用“{type}”,因为它是专用类型别名",
"classTypeParametersIllegal": "类类型参数语法需要 Python 3.12 或更高版本",

View File

@ -67,7 +67,6 @@
"classGetItemClsParam": "__class_getitem__ 覆寫應接受 \"cls\" 參數",
"classMethodClsParam": "類別方法應採用 \"cls\" 參數",
"classNotRuntimeSubscriptable": "類別 \"{name}\" 的下標會產生執行階段例外; 以引號括住類型註釋",
"classPatternBuiltInArgCount": "類別模式最多接受 1 個位置子模式",
"classPatternBuiltInArgPositional": "類別模式僅接受位置子模式",
"classPatternTypeAlias": "無法在類別模式中使用 \"{type}\",因為它是特殊的型別別名",
"classTypeParametersIllegal": "類別類型參數語法需要 Python 3.12 或更新版本",

View File

@ -0,0 +1,63 @@
# This sample tests the detection of too many positional patterns.
from dataclasses import dataclass
@dataclass
class A:
a: int
class B:
a: int
b: int
__match_args__ = ("a", "b")
class C(B):
...
class D(int): ...
def func1(subj: A | B):
match subj:
# This should generate an error because A accepts only
# one positional pattern.
case A(1, 2):
pass
case A(1):
pass
case A():
pass
case B(1, 2):
pass
# This should generate an error because B accepts only
# two positional patterns.
case B(1, 2, 3):
pass
# This should generate an error because B accepts only
# two positional patterns.
case C(1, 2, 3):
pass
case D(1):
pass
# This should generate an error because D accepts only
# one positional pattern.
case D(1, 2):
pass
case int(1):
pass
# This should generate an error because int accepts only
# one positional pattern.
case int(1, 2):
pass

View File

@ -1248,6 +1248,14 @@ test('MatchClass4', () => {
TestUtils.validateResults(analysisResults, 0);
});
test('MatchClass5', () => {
const configOptions = new ConfigOptions('.');
configOptions.defaultPythonVersion = PythonVersion.V3_10;
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['matchClass5.py'], configOptions);
TestUtils.validateResults(analysisResults, 5);
});
test('MatchValue1', () => {
const configOptions = new ConfigOptions('.');