diff --git a/packages/pyright-internal/src/analyzer/patternMatching.ts b/packages/pyright-internal/src/analyzer/patternMatching.ts index 2f7ef0624..513f26aeb 100644 --- a/packages/pyright-internal/src/analyzer/patternMatching.ts +++ b/packages/pyright-internal/src/analyzer/patternMatching.ts @@ -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] + ); + } } } diff --git a/packages/pyright-internal/src/localization/localize.ts b/packages/pyright-internal/src/localization/localize.ts index af4f23fff..dd3053879 100644 --- a/packages/pyright-internal/src/localization/localize.ts +++ b/packages/pyright-internal/src/localization/localize.ts @@ -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'); diff --git a/packages/pyright-internal/src/localization/package.nls.cs.json b/packages/pyright-internal/src/localization/package.nls.cs.json index 669c9f560..14e384b47 100644 --- a/packages/pyright-internal/src/localization/package.nls.cs.json +++ b/packages/pyright-internal/src/localization/package.nls.cs.json @@ -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ší", diff --git a/packages/pyright-internal/src/localization/package.nls.de.json b/packages/pyright-internal/src/localization/package.nls.de.json index ffa8dd520..05a5d5c2d 100644 --- a/packages/pyright-internal/src/localization/package.nls.de.json +++ b/packages/pyright-internal/src/localization/package.nls.de.json @@ -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.", diff --git a/packages/pyright-internal/src/localization/package.nls.en-us.json b/packages/pyright-internal/src/localization/package.nls.en-us.json index 8df008c64..489564fff 100644 --- a/packages/pyright-internal/src/localization/package.nls.en-us.json +++ b/packages/pyright-internal/src/localization/package.nls.en-us.json @@ -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", diff --git a/packages/pyright-internal/src/localization/package.nls.es.json b/packages/pyright-internal/src/localization/package.nls.es.json index 1693596aa..d63bf2b64 100644 --- a/packages/pyright-internal/src/localization/package.nls.es.json +++ b/packages/pyright-internal/src/localization/package.nls.es.json @@ -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.", diff --git a/packages/pyright-internal/src/localization/package.nls.fr.json b/packages/pyright-internal/src/localization/package.nls.fr.json index 64b28272c..bea417054 100644 --- a/packages/pyright-internal/src/localization/package.nls.fr.json +++ b/packages/pyright-internal/src/localization/package.nls.fr.json @@ -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": "L’indice de la classe « {name} » génère une exception d’exécution ; placer l’annotation 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", diff --git a/packages/pyright-internal/src/localization/package.nls.it.json b/packages/pyright-internal/src/localization/package.nls.it.json index 9c5e1727f..5be4f3a6f 100644 --- a/packages/pyright-internal/src/localization/package.nls.it.json +++ b/packages/pyright-internal/src/localization/package.nls.it.json @@ -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", diff --git a/packages/pyright-internal/src/localization/package.nls.ja.json b/packages/pyright-internal/src/localization/package.nls.ja.json index 22c510c9a..7a6774465 100644 --- a/packages/pyright-internal/src/localization/package.nls.ja.json +++ b/packages/pyright-internal/src/localization/package.nls.ja.json @@ -67,7 +67,6 @@ "classGetItemClsParam": "__class_getitem__ override は \"cls\" パラメーターを受け取る必要があります", "classMethodClsParam": "クラス メソッドは \"cls\" パラメーターを受け取る必要があります", "classNotRuntimeSubscriptable": "クラス \"{name}\" の添字はランタイム例外を生成します。型の注釈を引用符で囲む", - "classPatternBuiltInArgCount": "クラス パターンは、最大 1 つの位置指定サブパターンを受け入れます", "classPatternBuiltInArgPositional": "クラス パターンは位置指定サブパターンのみを受け入れます", "classPatternTypeAlias": "\"{type}\" は特殊な型エイリアスであるため、クラス パターンでは使用できません", "classTypeParametersIllegal": "クラス型パラメーターの構文には Python 3.12 以降が必要です", diff --git a/packages/pyright-internal/src/localization/package.nls.ko.json b/packages/pyright-internal/src/localization/package.nls.ko.json index 86e24c4cf..3fecbf107 100644 --- a/packages/pyright-internal/src/localization/package.nls.ko.json +++ b/packages/pyright-internal/src/localization/package.nls.ko.json @@ -67,7 +67,6 @@ "classGetItemClsParam": "__class_getitem__ 재정의는 \"cls\" 매개 변수를 사용해야 합니다.", "classMethodClsParam": "클래스 메서드는 ‘cls’ 매개 변수를 사용해야 합니다.", "classNotRuntimeSubscriptable": "클래스 \"{name}\"에 대한 첨자는 런타임 예외를 생성합니다. 따옴표로 형식 주석 묶기", - "classPatternBuiltInArgCount": "클래스 패턴은 최대 1개의 위치 하위 패턴을 허용합니다.", "classPatternBuiltInArgPositional": "클래스 패턴은 위치 하위 패턴만 허용합니다.", "classPatternTypeAlias": "‘{type}’은(는) 특수 형식 별칭이므로 클래스 패턴에서 사용할 수 없습니다.", "classTypeParametersIllegal": "클래스 형식 매개 변수 구문에는 Python 3.12 이상이 필요합니다.", diff --git a/packages/pyright-internal/src/localization/package.nls.pl.json b/packages/pyright-internal/src/localization/package.nls.pl.json index 965eb8cf9..3ea718e2c 100644 --- a/packages/pyright-internal/src/localization/package.nls.pl.json +++ b/packages/pyright-internal/src/localization/package.nls.pl.json @@ -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", diff --git a/packages/pyright-internal/src/localization/package.nls.pt-br.json b/packages/pyright-internal/src/localization/package.nls.pt-br.json index 5cabe1a00..25e7955d4 100644 --- a/packages/pyright-internal/src/localization/package.nls.pt-br.json +++ b/packages/pyright-internal/src/localization/package.nls.pt-br.json @@ -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", diff --git a/packages/pyright-internal/src/localization/package.nls.qps-ploc.json b/packages/pyright-internal/src/localization/package.nls.qps-ploc.json index 72f3844d6..5f134639c 100644 --- a/packages/pyright-internal/src/localization/package.nls.qps-ploc.json +++ b/packages/pyright-internal/src/localization/package.nls.qps-ploc.json @@ -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Ấğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्นั้ढूँ]", diff --git a/packages/pyright-internal/src/localization/package.nls.ru.json b/packages/pyright-internal/src/localization/package.nls.ru.json index 1c44a2505..9869838ac 100644 --- a/packages/pyright-internal/src/localization/package.nls.ru.json +++ b/packages/pyright-internal/src/localization/package.nls.ru.json @@ -67,7 +67,6 @@ "classGetItemClsParam": "Переопределение метода __class_getitem__ должно принимать параметр \"cls\"", "classMethodClsParam": "Методы класса должны принимать параметр cls", "classNotRuntimeSubscriptable": "Операция взятия подстроки для класса \"{name}\" создаст исключение среды выполнения; заключите заметку типа в кавычки", - "classPatternBuiltInArgCount": "Шаблон класса принимает не более одного позиционного вложенного шаблона", "classPatternBuiltInArgPositional": "Шаблон класса принимает только позиционный вложенный шаблон", "classPatternTypeAlias": "\"{type}\" нельзя использовать в шаблоне класса, поскольку это псевдоним специализированного типа", "classTypeParametersIllegal": "Синтаксис параметра типа класса может использоваться в Python версии не ниже 3.12.", diff --git a/packages/pyright-internal/src/localization/package.nls.tr.json b/packages/pyright-internal/src/localization/package.nls.tr.json index c96905662..e39b96c0e 100644 --- a/packages/pyright-internal/src/localization/package.nls.tr.json +++ b/packages/pyright-internal/src/localization/package.nls.tr.json @@ -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", diff --git a/packages/pyright-internal/src/localization/package.nls.zh-cn.json b/packages/pyright-internal/src/localization/package.nls.zh-cn.json index db4c8d412..333a1a09d 100644 --- a/packages/pyright-internal/src/localization/package.nls.zh-cn.json +++ b/packages/pyright-internal/src/localization/package.nls.zh-cn.json @@ -67,7 +67,6 @@ "classGetItemClsParam": "__class_getitem__替代应采用“cls”参数", "classMethodClsParam": "类方法应采用“cls”参数", "classNotRuntimeSubscriptable": "类“{name}”的下标将生成运行时异常;将类型批注括在引号中", - "classPatternBuiltInArgCount": "类模式最多接受 1 个位置子模式", "classPatternBuiltInArgPositional": "类模式仅接受位置子模式", "classPatternTypeAlias": "无法在类模式中使用“{type}”,因为它是专用类型别名", "classTypeParametersIllegal": "类类型参数语法需要 Python 3.12 或更高版本", diff --git a/packages/pyright-internal/src/localization/package.nls.zh-tw.json b/packages/pyright-internal/src/localization/package.nls.zh-tw.json index b95a7762e..301093f15 100644 --- a/packages/pyright-internal/src/localization/package.nls.zh-tw.json +++ b/packages/pyright-internal/src/localization/package.nls.zh-tw.json @@ -67,7 +67,6 @@ "classGetItemClsParam": "__class_getitem__ 覆寫應接受 \"cls\" 參數", "classMethodClsParam": "類別方法應採用 \"cls\" 參數", "classNotRuntimeSubscriptable": "類別 \"{name}\" 的下標會產生執行階段例外; 以引號括住類型註釋", - "classPatternBuiltInArgCount": "類別模式最多接受 1 個位置子模式", "classPatternBuiltInArgPositional": "類別模式僅接受位置子模式", "classPatternTypeAlias": "無法在類別模式中使用 \"{type}\",因為它是特殊的型別別名", "classTypeParametersIllegal": "類別類型參數語法需要 Python 3.12 或更新版本", diff --git a/packages/pyright-internal/src/tests/samples/matchClass5.py b/packages/pyright-internal/src/tests/samples/matchClass5.py new file mode 100644 index 000000000..1503305ae --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/matchClass5.py @@ -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 diff --git a/packages/pyright-internal/src/tests/typeEvaluator3.test.ts b/packages/pyright-internal/src/tests/typeEvaluator3.test.ts index e979a37f3..36c516205 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator3.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator3.test.ts @@ -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('.');