Skip to content

Commit

Permalink
Added missing syntax error check for positional argument that follows…
Browse files Browse the repository at this point in the history
… an unpacked keyword argument in a call expression. Also added a check for an unpack operator used within the first argument of a `cast` call. This addresses #9674. (#9675)
  • Loading branch information
erictraut authored Jan 8, 2025
1 parent b0bd5bf commit cba1872
Show file tree
Hide file tree
Showing 6 changed files with 25 additions and 3 deletions.
8 changes: 8 additions & 0 deletions packages/pyright-internal/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10452,6 +10452,14 @@ export function createTypeEvaluator(

// Evaluates the type of the "cast" call.
function evaluateCastCall(argList: Arg[], errorNode: ExpressionNode) {
if (argList[0].argCategory !== ArgCategory.Simple && argList[0].valueExpression) {
addDiagnostic(
DiagnosticRule.reportInvalidTypeForm,
LocMessage.unpackInAnnotation(),
argList[0].valueExpression
);
}

// Verify that the cast is necessary.
let castToType = getTypeOfArgExpectingType(argList[0], { typeExpression: true }).type;

Expand Down
1 change: 1 addition & 0 deletions packages/pyright-internal/src/localization/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,7 @@ export namespace Localizer {
export const patternNeverMatches = () =>
new ParameterizedString<{ type: string }>(getRawString('Diagnostic.patternNeverMatches'));
export const positionArgAfterNamedArg = () => getRawString('Diagnostic.positionArgAfterNamedArg');
export const positionArgAfterUnpackedDictArg = () => getRawString('Diagnostic.positionArgAfterUnpackedDictArg');
export const privateImportFromPyTypedModule = () =>
new ParameterizedString<{ name: string; module: string }>(
getRawString('Diagnostic.privateImportFromPyTypedModule')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,7 @@
},
"patternNeverMatches": "Pattern will never be matched for subject type \"{type}\"",
"positionArgAfterNamedArg": "Positional argument cannot appear after keyword arguments",
"positionArgAfterUnpackedDictArg": "Positional argument cannot appear after keyword argument unpacking",
"positionOnlyAfterArgs": "Position-only parameter separator not allowed after \"*\" parameter",
"positionOnlyAfterKeywordOnly": "\"/\" parameter must appear before \"*\" parameter",
"positionOnlyAfterNon": "Position-only parameter not allowed after parameter that is not position-only",
Expand Down
14 changes: 12 additions & 2 deletions packages/pyright-internal/src/parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3862,6 +3862,7 @@ export class Parser {
private _parseArgList(): ArgListResult {
const argList: ArgumentNode[] = [];
let sawKeywordArg = false;
let sawUnpackedKeywordArg = false;
let trailingComma = false;

while (true) {
Expand All @@ -3878,8 +3879,17 @@ export class Parser {
const arg = this._parseArgument();
if (arg.d.name) {
sawKeywordArg = true;
} else if (sawKeywordArg && arg.d.argCategory === ArgCategory.Simple) {
this._addSyntaxError(LocMessage.positionArgAfterNamedArg(), arg);
} else {
if (sawKeywordArg && arg.d.argCategory === ArgCategory.Simple) {
this._addSyntaxError(LocMessage.positionArgAfterNamedArg(), arg);
}

if (sawUnpackedKeywordArg && arg.d.argCategory !== ArgCategory.UnpackedDictionary) {
this._addSyntaxError(LocMessage.positionArgAfterUnpackedDictArg(), arg);
}
}
if (arg.d.argCategory === ArgCategory.UnpackedDictionary) {
sawUnpackedKeywordArg = true;
}
argList.push(arg);

Expand Down
2 changes: 2 additions & 0 deletions packages/pyright-internal/src/tests/samples/paramSpec49.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def inner0(*args: P.args, **kwargs: P.kwargs) -> None:
self.dispatcher.dispatch(stub, 1, *args, **kwargs)

def inner1(*args: P.args, **kwargs: P.kwargs) -> None:
# This should generate an error because a positional argument
# cannot appear after an unpacked keyword argument.
self.dispatcher.dispatch(stub, 1, **kwargs, *args)

def inner2(*args: P.args, **kwargs: P.kwargs) -> None:
Expand Down
2 changes: 1 addition & 1 deletion packages/pyright-internal/src/tests/typeEvaluator4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ test('ParamSpec48', () => {

test('ParamSpec49', () => {
const results = TestUtils.typeAnalyzeSampleFiles(['paramSpec49.py']);
TestUtils.validateResults(results, 7);
TestUtils.validateResults(results, 8);
});

test('ParamSpec50', () => {
Expand Down

0 comments on commit cba1872

Please sign in to comment.