mirror of
https://github.com/microsoft/pyright.git
synced 2024-10-06 12:57:14 +03:00
Added support for auto-synthesized __replace__
method in dataclass … (#8514)
* Added support for auto-synthesized `__replace__` method in dataclass and namedtuple classes, a new feature in Python 3.13. This addresses #8300. * Fixed broken test on Linux due to file system case sensitivity.
This commit is contained in:
parent
39757e4519
commit
77684ec7fc
@ -11,6 +11,7 @@
|
|||||||
import { assert } from '../common/debug';
|
import { assert } from '../common/debug';
|
||||||
import { DiagnosticAddendum } from '../common/diagnostic';
|
import { DiagnosticAddendum } from '../common/diagnostic';
|
||||||
import { DiagnosticRule } from '../common/diagnosticRules';
|
import { DiagnosticRule } from '../common/diagnosticRules';
|
||||||
|
import { pythonVersion3_13 } from '../common/pythonVersion';
|
||||||
import { LocMessage } from '../localization/localize';
|
import { LocMessage } from '../localization/localize';
|
||||||
import {
|
import {
|
||||||
ArgumentCategory,
|
ArgumentCategory,
|
||||||
@ -108,18 +109,23 @@ export function synthesizeDataClassMethods(
|
|||||||
}
|
}
|
||||||
newType.shared.declaredReturnType = convertToInstance(classTypeVar);
|
newType.shared.declaredReturnType = convertToInstance(classTypeVar);
|
||||||
|
|
||||||
const selfParam = FunctionParam.create(
|
const selfType = synthesizeTypeVarForSelfCls(classType, /* isClsParam */ false);
|
||||||
ParameterCategory.Simple,
|
const selfParam = FunctionParam.create(ParameterCategory.Simple, selfType, FunctionParamFlags.TypeDeclared, 'self');
|
||||||
synthesizeTypeVarForSelfCls(classType, /* isClsParam */ false),
|
|
||||||
FunctionParamFlags.TypeDeclared,
|
|
||||||
'self'
|
|
||||||
);
|
|
||||||
FunctionType.addParameter(initType, selfParam);
|
FunctionType.addParameter(initType, selfParam);
|
||||||
if (isNamedTuple) {
|
if (isNamedTuple) {
|
||||||
FunctionType.addDefaultParameters(initType);
|
FunctionType.addDefaultParameters(initType);
|
||||||
}
|
}
|
||||||
initType.shared.declaredReturnType = evaluator.getNoneType();
|
initType.shared.declaredReturnType = evaluator.getNoneType();
|
||||||
|
|
||||||
|
// For Python 3.13 and newer, synthesize a __replace__ method.
|
||||||
|
let replaceType: FunctionType | undefined;
|
||||||
|
if (AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.pythonVersion >= pythonVersion3_13) {
|
||||||
|
replaceType = FunctionType.createSynthesizedInstance('__replace__');
|
||||||
|
FunctionType.addParameter(replaceType, selfParam);
|
||||||
|
FunctionType.addKeywordOnlyParameterSeparator(replaceType);
|
||||||
|
replaceType.shared.declaredReturnType = selfType;
|
||||||
|
}
|
||||||
|
|
||||||
// Maintain a list of all dataclass entries (including
|
// Maintain a list of all dataclass entries (including
|
||||||
// those from inherited classes) plus a list of only those
|
// those from inherited classes) plus a list of only those
|
||||||
// entries added by this class.
|
// entries added by this class.
|
||||||
@ -132,6 +138,10 @@ export function synthesizeDataClassMethods(
|
|||||||
// safely determine the parameter list, so we'll accept any parameters
|
// safely determine the parameter list, so we'll accept any parameters
|
||||||
// to avoid a false positive.
|
// to avoid a false positive.
|
||||||
FunctionType.addDefaultParameters(initType);
|
FunctionType.addDefaultParameters(initType);
|
||||||
|
|
||||||
|
if (replaceType) {
|
||||||
|
FunctionType.addDefaultParameters(replaceType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add field-based parameters to either the __new__ or __init__ method
|
// Add field-based parameters to either the __new__ or __init__ method
|
||||||
@ -536,6 +546,14 @@ export function synthesizeDataClassMethods(
|
|||||||
} else {
|
} else {
|
||||||
FunctionType.addParameter(constructorType, functionParam);
|
FunctionType.addParameter(constructorType, functionParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (replaceType) {
|
||||||
|
const paramWithDefault = {
|
||||||
|
...functionParam,
|
||||||
|
defaultType: AnyType.create(/* isEllipsis */ true),
|
||||||
|
};
|
||||||
|
FunctionType.addParameter(replaceType, paramWithDefault);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -549,6 +567,10 @@ export function synthesizeDataClassMethods(
|
|||||||
|
|
||||||
symbolTable.set('__init__', Symbol.createWithType(SymbolFlags.ClassMember, initType));
|
symbolTable.set('__init__', Symbol.createWithType(SymbolFlags.ClassMember, initType));
|
||||||
symbolTable.set('__new__', Symbol.createWithType(SymbolFlags.ClassMember, newType));
|
symbolTable.set('__new__', Symbol.createWithType(SymbolFlags.ClassMember, newType));
|
||||||
|
|
||||||
|
if (replaceType) {
|
||||||
|
symbolTable.set('__replace__', Symbol.createWithType(SymbolFlags.ClassMember, replaceType));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synthesize the __match_args__ class variable if it doesn't exist.
|
// Synthesize the __match_args__ class variable if it doesn't exist.
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
# This sample tests the synthesis of a "__replace__" method for dataclass
|
||||||
|
# classes in Python 3.13 and newer.
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DC1:
|
||||||
|
a: int
|
||||||
|
b: str
|
||||||
|
c: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
dc1: DC1 = DC1(1, "")
|
||||||
|
|
||||||
|
dc1_clone = dc1.__replace__(b="", a=1, c="")
|
||||||
|
reveal_type(dc1_clone, expected_text="DC1")
|
||||||
|
|
||||||
|
dc1.__replace__(c="")
|
||||||
|
dc1.__replace__(b="2")
|
||||||
|
|
||||||
|
# This should generate an error.
|
||||||
|
dc1.__replace__(b=2)
|
||||||
|
|
||||||
|
# This should generate an error.
|
||||||
|
dc1.__replace__(d="")
|
||||||
|
|
||||||
|
|
||||||
|
class NT1(NamedTuple):
|
||||||
|
a: int
|
||||||
|
b: str
|
||||||
|
c: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
nt1 = NT1(1, "")
|
||||||
|
|
||||||
|
nt1_clone = nt1.__replace__(c="")
|
||||||
|
reveal_type(nt1_clone, expected_text="NT1")
|
||||||
|
|
||||||
|
# This should generate an error.
|
||||||
|
nt1.__replace__(b=2)
|
||||||
|
|
||||||
|
# This should generate an error.
|
||||||
|
nt1.__replace__(d="")
|
@ -13,6 +13,7 @@ import {
|
|||||||
pythonVersion3_10,
|
pythonVersion3_10,
|
||||||
pythonVersion3_11,
|
pythonVersion3_11,
|
||||||
pythonVersion3_12,
|
pythonVersion3_12,
|
||||||
|
pythonVersion3_13,
|
||||||
pythonVersion3_7,
|
pythonVersion3_7,
|
||||||
pythonVersion3_8,
|
pythonVersion3_8,
|
||||||
pythonVersion3_9,
|
pythonVersion3_9,
|
||||||
@ -365,6 +366,17 @@ test('DataClass17', () => {
|
|||||||
TestUtils.validateResults(analysisResults, 5);
|
TestUtils.validateResults(analysisResults, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('DataClassReplace1', () => {
|
||||||
|
const configOptions = new ConfigOptions(Uri.empty());
|
||||||
|
|
||||||
|
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['dataclassReplace1.py'], configOptions);
|
||||||
|
TestUtils.validateResults(analysisResults1, 10);
|
||||||
|
|
||||||
|
configOptions.defaultPythonVersion = pythonVersion3_13;
|
||||||
|
const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['dataclassReplace1.py'], configOptions);
|
||||||
|
TestUtils.validateResults(analysisResults2, 4);
|
||||||
|
});
|
||||||
|
|
||||||
test('DataClassFrozen1', () => {
|
test('DataClassFrozen1', () => {
|
||||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['dataclassFrozen1.py']);
|
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['dataclassFrozen1.py']);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user