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 { DiagnosticAddendum } from '../common/diagnostic';
|
||||
import { DiagnosticRule } from '../common/diagnosticRules';
|
||||
import { pythonVersion3_13 } from '../common/pythonVersion';
|
||||
import { LocMessage } from '../localization/localize';
|
||||
import {
|
||||
ArgumentCategory,
|
||||
@ -108,18 +109,23 @@ export function synthesizeDataClassMethods(
|
||||
}
|
||||
newType.shared.declaredReturnType = convertToInstance(classTypeVar);
|
||||
|
||||
const selfParam = FunctionParam.create(
|
||||
ParameterCategory.Simple,
|
||||
synthesizeTypeVarForSelfCls(classType, /* isClsParam */ false),
|
||||
FunctionParamFlags.TypeDeclared,
|
||||
'self'
|
||||
);
|
||||
const selfType = synthesizeTypeVarForSelfCls(classType, /* isClsParam */ false);
|
||||
const selfParam = FunctionParam.create(ParameterCategory.Simple, selfType, FunctionParamFlags.TypeDeclared, 'self');
|
||||
FunctionType.addParameter(initType, selfParam);
|
||||
if (isNamedTuple) {
|
||||
FunctionType.addDefaultParameters(initType);
|
||||
}
|
||||
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
|
||||
// those from inherited classes) plus a list of only those
|
||||
// entries added by this class.
|
||||
@ -132,6 +138,10 @@ export function synthesizeDataClassMethods(
|
||||
// safely determine the parameter list, so we'll accept any parameters
|
||||
// to avoid a false positive.
|
||||
FunctionType.addDefaultParameters(initType);
|
||||
|
||||
if (replaceType) {
|
||||
FunctionType.addDefaultParameters(replaceType);
|
||||
}
|
||||
}
|
||||
|
||||
// Add field-based parameters to either the __new__ or __init__ method
|
||||
@ -536,6 +546,14 @@ export function synthesizeDataClassMethods(
|
||||
} else {
|
||||
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('__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.
|
||||
|
@ -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_11,
|
||||
pythonVersion3_12,
|
||||
pythonVersion3_13,
|
||||
pythonVersion3_7,
|
||||
pythonVersion3_8,
|
||||
pythonVersion3_9,
|
||||
@ -365,6 +366,17 @@ test('DataClass17', () => {
|
||||
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', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['dataclassFrozen1.py']);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user