Skip to content

Commit

Permalink
Improved the synthesized __init__ method of a TypedDict class so …
Browse files Browse the repository at this point in the history
…it accepts another instance of the same TypedDict (or another TypedDict that is type compatible) as an argument. This makes pyright compatible with mypy in this regard. See python/mypy#8890 for details.
  • Loading branch information
msfterictraut committed Apr 9, 2023
1 parent 9f81564 commit 8de6fcb
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 6 deletions.
51 changes: 45 additions & 6 deletions packages/pyright-internal/src/analyzer/typedDicts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,18 +243,48 @@ export function synthesizeTypedDictClassMethods(
FunctionType.addDefaultParameters(newType);
newType.details.declaredReturnType = ClassType.cloneAsInstance(classType);

// Synthesize an __init__ method.
const initType = FunctionType.createSynthesizedInstance('__init__');
FunctionType.addParameter(initType, {
// Synthesize an __init__ method with two overrides.
const initOverride1 = FunctionType.createSynthesizedInstance('__init__', FunctionTypeFlags.Overloaded);
FunctionType.addParameter(initOverride1, {
category: ParameterCategory.Simple,
name: 'self',
type: ClassType.cloneAsInstance(classType),
hasDeclaredType: true,
});
initType.details.declaredReturnType = NoneType.createInstance();
initOverride1.details.declaredReturnType = NoneType.createInstance();

// The first parameter must be positional-only.
FunctionType.addParameter(initOverride1, {
category: ParameterCategory.Simple,
name: '__map',
type: ClassType.cloneAsInstance(classType),
hasDeclaredType: true,
});

FunctionType.addParameter(initOverride1, {
category: ParameterCategory.Simple,
name: '',
type: UnknownType.create(),
});

// All subsequent parameters must be named, so insert an empty "*".
FunctionType.addParameter(initOverride1, {
category: ParameterCategory.VarArgList,
type: AnyType.create(),
hasDeclaredType: true,
});

const initOverride2 = FunctionType.createSynthesizedInstance('__init__', FunctionTypeFlags.Overloaded);
FunctionType.addParameter(initOverride2, {
category: ParameterCategory.Simple,
name: 'self',
type: ClassType.cloneAsInstance(classType),
hasDeclaredType: true,
});
initOverride2.details.declaredReturnType = NoneType.createInstance();

// All parameters must be named, so insert an empty "*".
FunctionType.addParameter(initType, {
FunctionType.addParameter(initOverride2, {
category: ParameterCategory.VarArgList,
type: AnyType.create(),
hasDeclaredType: true,
Expand All @@ -263,7 +293,15 @@ export function synthesizeTypedDictClassMethods(
const entries = getTypedDictMembersForClass(evaluator, classType);
let allEntriesAreNotRequired = true;
entries.forEach((entry, name) => {
FunctionType.addParameter(initType, {
FunctionType.addParameter(initOverride1, {
category: ParameterCategory.Simple,
name,
hasDefault: true,
type: entry.valueType,
hasDeclaredType: true,
});

FunctionType.addParameter(initOverride2, {
category: ParameterCategory.Simple,
name,
hasDefault: !entry.isRequired,
Expand All @@ -277,6 +315,7 @@ export function synthesizeTypedDictClassMethods(
});

const symbolTable = classType.details.fields;
const initType = OverloadedFunctionType.create([initOverride1, initOverride2]);
symbolTable.set('__init__', Symbol.createWithType(SymbolFlags.ClassMember, initType));
symbolTable.set('__new__', Symbol.createWithType(SymbolFlags.ClassMember, newType));

Expand Down
3 changes: 3 additions & 0 deletions packages/pyright-internal/src/tests/samples/typedDict2.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ def get_movie_name(movie: Movie):
"name2": "Blade Runner"
}

movie5: Movie = Movie(movie3)
movie6: Movie = Movie(movie3, year=2030, name="New movie")

book1: BookBasedMovie = {"name": "Moonraker", "year": 1979, "based_on": "Moonraker"}

book2: BookBasedMovie = {"year": 1979, "based_on": "Moonraker"}
Expand Down

0 comments on commit 8de6fcb

Please sign in to comment.