Skip to content

Commit

Permalink
feat: Use exact types for fromPartial (#412)
Browse files Browse the repository at this point in the history
* Add proof-of-concept exact types.

Fixes #156.

* Extract Builtin.

* Codegen.

* Add expect error.

* Codegen again.

* Fix type registry.
  • Loading branch information
stephenh authored Nov 28, 2021
1 parent 4abc820 commit 808f8a7
Show file tree
Hide file tree
Showing 75 changed files with 845 additions and 338 deletions.
8 changes: 7 additions & 1 deletion integration/angular/simple-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,15 @@ export const SimpleMessage = {
return obj;
},

fromPartial(object: DeepPartial<SimpleMessage>): SimpleMessage {
fromPartial<I extends Exact<DeepPartial<SimpleMessage>, I>>(object: I): SimpleMessage {
const message = { ...baseSimpleMessage } as SimpleMessage;
message.numberField = object.numberField ?? 0;
return message;
},
};

type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;

export type DeepPartial<T> = T extends Builtin
? T
: T extends Array<infer U>
Expand All @@ -67,6 +68,11 @@ export type DeepPartial<T> = T extends Builtin
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;

type KeysOfUnion<T> = T extends T ? keyof T : never;
export type Exact<P, I extends P> = P extends Builtin
? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & Record<Exclude<keyof I, KeysOfUnion<P>>, never>;

// If you get a compile-error about 'Constructor<Long> and ... have no overlap',
// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'.
if (util.Long !== Long) {
Expand Down
10 changes: 8 additions & 2 deletions integration/avoid-import-conflicts/simple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const Simple = {
return obj;
},

fromPartial(object: DeepPartial<Simple>): Simple {
fromPartial<I extends Exact<DeepPartial<Simple>, I>>(object: I): Simple {
const message = { ...baseSimple } as Simple;
message.name = object.name ?? '';
message.otherSimple =
Expand Down Expand Up @@ -171,7 +171,7 @@ export const SimpleEnums = {
return obj;
},

fromPartial(object: DeepPartial<SimpleEnums>): SimpleEnums {
fromPartial<I extends Exact<DeepPartial<SimpleEnums>, I>>(object: I): SimpleEnums {
const message = { ...baseSimpleEnums } as SimpleEnums;
message.localEnum = object.localEnum ?? 0;
message.importEnum = object.importEnum ?? 0;
Expand All @@ -180,6 +180,7 @@ export const SimpleEnums = {
};

type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;

export type DeepPartial<T> = T extends Builtin
? T
: T extends Array<infer U>
Expand All @@ -190,6 +191,11 @@ export type DeepPartial<T> = T extends Builtin
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;

type KeysOfUnion<T> = T extends T ? keyof T : never;
export type Exact<P, I extends P> = P extends Builtin
? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & Record<Exclude<keyof I, KeysOfUnion<P>>, never>;

// If you get a compile-error about 'Constructor<Long> and ... have no overlap',
// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'.
if (util.Long !== Long) {
Expand Down
8 changes: 7 additions & 1 deletion integration/avoid-import-conflicts/simple2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const Simple = {
return obj;
},

fromPartial(object: DeepPartial<Simple>): Simple {
fromPartial<I extends Exact<DeepPartial<Simple>, I>>(object: I): Simple {
const message = { ...baseSimple } as Simple;
message.name = object.name ?? '';
message.age = object.age ?? 0;
Expand All @@ -104,6 +104,7 @@ export const Simple = {
};

type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;

export type DeepPartial<T> = T extends Builtin
? T
: T extends Array<infer U>
Expand All @@ -114,6 +115,11 @@ export type DeepPartial<T> = T extends Builtin
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;

type KeysOfUnion<T> = T extends T ? keyof T : never;
export type Exact<P, I extends P> = P extends Builtin
? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & Record<Exclude<keyof I, KeysOfUnion<P>>, never>;

// If you get a compile-error about 'Constructor<Long> and ... have no overlap',
// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'.
if (util.Long !== Long) {
Expand Down
8 changes: 7 additions & 1 deletion integration/barrel-imports/bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const Bar = {
return obj;
},

fromPartial(object: DeepPartial<Bar>): Bar {
fromPartial<I extends Exact<DeepPartial<Bar>, I>>(object: I): Bar {
const message = { ...baseBar } as Bar;
message.name = object.name ?? '';
message.age = object.age ?? 0;
Expand All @@ -64,6 +64,7 @@ export const Bar = {
};

type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;

type DeepPartial<T> = T extends Builtin
? T
: T extends Array<infer U>
Expand All @@ -74,6 +75,11 @@ type DeepPartial<T> = T extends Builtin
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;

type KeysOfUnion<T> = T extends T ? keyof T : never;
type Exact<P, I extends P> = P extends Builtin
? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & Record<Exclude<keyof I, KeysOfUnion<P>>, never>;

// If you get a compile-error about 'Constructor<Long> and ... have no overlap',
// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'.
if (util.Long !== Long) {
Expand Down
8 changes: 7 additions & 1 deletion integration/barrel-imports/foo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const Foo = {
return obj;
},

fromPartial(object: DeepPartial<Foo>): Foo {
fromPartial<I extends Exact<DeepPartial<Foo>, I>>(object: I): Foo {
const message = { ...baseFoo } as Foo;
message.name = object.name ?? '';
message.bar = object.bar !== undefined && object.bar !== null ? Bar.fromPartial(object.bar) : undefined;
Expand All @@ -65,6 +65,7 @@ export const Foo = {
};

type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;

type DeepPartial<T> = T extends Builtin
? T
: T extends Array<infer U>
Expand All @@ -75,6 +76,11 @@ type DeepPartial<T> = T extends Builtin
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;

type KeysOfUnion<T> = T extends T ? keyof T : never;
type Exact<P, I extends P> = P extends Builtin
? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & Record<Exclude<keyof I, KeysOfUnion<P>>, never>;

// If you get a compile-error about 'Constructor<Long> and ... have no overlap',
// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'.
if (util.Long !== Long) {
Expand Down
34 changes: 21 additions & 13 deletions integration/batching-with-context/batching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ export const BatchQueryRequest = {
return obj;
},

fromPartial(object: DeepPartial<BatchQueryRequest>): BatchQueryRequest {
fromPartial<I extends Exact<DeepPartial<BatchQueryRequest>, I>>(object: I): BatchQueryRequest {
const message = { ...baseBatchQueryRequest } as BatchQueryRequest;
message.ids = (object.ids ?? []).map((e) => e);
message.ids = object.ids?.map((e) => e) || [];
return message;
},
};
Expand Down Expand Up @@ -143,9 +143,9 @@ export const BatchQueryResponse = {
return obj;
},

fromPartial(object: DeepPartial<BatchQueryResponse>): BatchQueryResponse {
fromPartial<I extends Exact<DeepPartial<BatchQueryResponse>, I>>(object: I): BatchQueryResponse {
const message = { ...baseBatchQueryResponse } as BatchQueryResponse;
message.entities = (object.entities ?? []).map((e) => Entity.fromPartial(e));
message.entities = object.entities?.map((e) => Entity.fromPartial(e)) || [];
return message;
},
};
Expand Down Expand Up @@ -195,9 +195,9 @@ export const BatchMapQueryRequest = {
return obj;
},

fromPartial(object: DeepPartial<BatchMapQueryRequest>): BatchMapQueryRequest {
fromPartial<I extends Exact<DeepPartial<BatchMapQueryRequest>, I>>(object: I): BatchMapQueryRequest {
const message = { ...baseBatchMapQueryRequest } as BatchMapQueryRequest;
message.ids = (object.ids ?? []).map((e) => e);
message.ids = object.ids?.map((e) => e) || [];
return message;
},
};
Expand Down Expand Up @@ -254,7 +254,7 @@ export const BatchMapQueryResponse = {
return obj;
},

fromPartial(object: DeepPartial<BatchMapQueryResponse>): BatchMapQueryResponse {
fromPartial<I extends Exact<DeepPartial<BatchMapQueryResponse>, I>>(object: I): BatchMapQueryResponse {
const message = { ...baseBatchMapQueryResponse } as BatchMapQueryResponse;
message.entities = Object.entries(object.entities ?? {}).reduce<{ [key: string]: Entity }>((acc, [key, value]) => {
if (value !== undefined) {
Expand Down Expand Up @@ -314,7 +314,9 @@ export const BatchMapQueryResponse_EntitiesEntry = {
return obj;
},

fromPartial(object: DeepPartial<BatchMapQueryResponse_EntitiesEntry>): BatchMapQueryResponse_EntitiesEntry {
fromPartial<I extends Exact<DeepPartial<BatchMapQueryResponse_EntitiesEntry>, I>>(
object: I
): BatchMapQueryResponse_EntitiesEntry {
const message = { ...baseBatchMapQueryResponse_EntitiesEntry } as BatchMapQueryResponse_EntitiesEntry;
message.key = object.key ?? '';
message.value = object.value !== undefined && object.value !== null ? Entity.fromPartial(object.value) : undefined;
Expand Down Expand Up @@ -362,7 +364,7 @@ export const GetOnlyMethodRequest = {
return obj;
},

fromPartial(object: DeepPartial<GetOnlyMethodRequest>): GetOnlyMethodRequest {
fromPartial<I extends Exact<DeepPartial<GetOnlyMethodRequest>, I>>(object: I): GetOnlyMethodRequest {
const message = { ...baseGetOnlyMethodRequest } as GetOnlyMethodRequest;
message.id = object.id ?? '';
return message;
Expand Down Expand Up @@ -409,7 +411,7 @@ export const GetOnlyMethodResponse = {
return obj;
},

fromPartial(object: DeepPartial<GetOnlyMethodResponse>): GetOnlyMethodResponse {
fromPartial<I extends Exact<DeepPartial<GetOnlyMethodResponse>, I>>(object: I): GetOnlyMethodResponse {
const message = { ...baseGetOnlyMethodResponse } as GetOnlyMethodResponse;
message.entity =
object.entity !== undefined && object.entity !== null ? Entity.fromPartial(object.entity) : undefined;
Expand Down Expand Up @@ -457,7 +459,7 @@ export const WriteMethodRequest = {
return obj;
},

fromPartial(object: DeepPartial<WriteMethodRequest>): WriteMethodRequest {
fromPartial<I extends Exact<DeepPartial<WriteMethodRequest>, I>>(object: I): WriteMethodRequest {
const message = { ...baseWriteMethodRequest } as WriteMethodRequest;
message.id = object.id ?? '';
return message;
Expand Down Expand Up @@ -496,7 +498,7 @@ export const WriteMethodResponse = {
return obj;
},

fromPartial(_: DeepPartial<WriteMethodResponse>): WriteMethodResponse {
fromPartial<I extends Exact<DeepPartial<WriteMethodResponse>, I>>(_: I): WriteMethodResponse {
const message = { ...baseWriteMethodResponse } as WriteMethodResponse;
return message;
},
Expand Down Expand Up @@ -550,7 +552,7 @@ export const Entity = {
return obj;
},

fromPartial(object: DeepPartial<Entity>): Entity {
fromPartial<I extends Exact<DeepPartial<Entity>, I>>(object: I): Entity {
const message = { ...baseEntity } as Entity;
message.id = object.id ?? '';
message.name = object.name ?? '';
Expand Down Expand Up @@ -656,6 +658,7 @@ export interface DataLoaders {
}

type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;

export type DeepPartial<T> = T extends Builtin
? T
: T extends Array<infer U>
Expand All @@ -666,6 +669,11 @@ export type DeepPartial<T> = T extends Builtin
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;

type KeysOfUnion<T> = T extends T ? keyof T : never;
export type Exact<P, I extends P> = P extends Builtin
? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & Record<Exclude<keyof I, KeysOfUnion<P>>, never>;

// If you get a compile-error about 'Constructor<Long> and ... have no overlap',
// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'.
if (util.Long !== Long) {
Expand Down
34 changes: 21 additions & 13 deletions integration/batching/batching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ export const BatchQueryRequest = {
return obj;
},

fromPartial(object: DeepPartial<BatchQueryRequest>): BatchQueryRequest {
fromPartial<I extends Exact<DeepPartial<BatchQueryRequest>, I>>(object: I): BatchQueryRequest {
const message = { ...baseBatchQueryRequest } as BatchQueryRequest;
message.ids = (object.ids ?? []).map((e) => e);
message.ids = object.ids?.map((e) => e) || [];
return message;
},
};
Expand Down Expand Up @@ -141,9 +141,9 @@ export const BatchQueryResponse = {
return obj;
},

fromPartial(object: DeepPartial<BatchQueryResponse>): BatchQueryResponse {
fromPartial<I extends Exact<DeepPartial<BatchQueryResponse>, I>>(object: I): BatchQueryResponse {
const message = { ...baseBatchQueryResponse } as BatchQueryResponse;
message.entities = (object.entities ?? []).map((e) => Entity.fromPartial(e));
message.entities = object.entities?.map((e) => Entity.fromPartial(e)) || [];
return message;
},
};
Expand Down Expand Up @@ -193,9 +193,9 @@ export const BatchMapQueryRequest = {
return obj;
},

fromPartial(object: DeepPartial<BatchMapQueryRequest>): BatchMapQueryRequest {
fromPartial<I extends Exact<DeepPartial<BatchMapQueryRequest>, I>>(object: I): BatchMapQueryRequest {
const message = { ...baseBatchMapQueryRequest } as BatchMapQueryRequest;
message.ids = (object.ids ?? []).map((e) => e);
message.ids = object.ids?.map((e) => e) || [];
return message;
},
};
Expand Down Expand Up @@ -252,7 +252,7 @@ export const BatchMapQueryResponse = {
return obj;
},

fromPartial(object: DeepPartial<BatchMapQueryResponse>): BatchMapQueryResponse {
fromPartial<I extends Exact<DeepPartial<BatchMapQueryResponse>, I>>(object: I): BatchMapQueryResponse {
const message = { ...baseBatchMapQueryResponse } as BatchMapQueryResponse;
message.entities = Object.entries(object.entities ?? {}).reduce<{ [key: string]: Entity }>((acc, [key, value]) => {
if (value !== undefined) {
Expand Down Expand Up @@ -312,7 +312,9 @@ export const BatchMapQueryResponse_EntitiesEntry = {
return obj;
},

fromPartial(object: DeepPartial<BatchMapQueryResponse_EntitiesEntry>): BatchMapQueryResponse_EntitiesEntry {
fromPartial<I extends Exact<DeepPartial<BatchMapQueryResponse_EntitiesEntry>, I>>(
object: I
): BatchMapQueryResponse_EntitiesEntry {
const message = { ...baseBatchMapQueryResponse_EntitiesEntry } as BatchMapQueryResponse_EntitiesEntry;
message.key = object.key ?? '';
message.value = object.value !== undefined && object.value !== null ? Entity.fromPartial(object.value) : undefined;
Expand Down Expand Up @@ -360,7 +362,7 @@ export const GetOnlyMethodRequest = {
return obj;
},

fromPartial(object: DeepPartial<GetOnlyMethodRequest>): GetOnlyMethodRequest {
fromPartial<I extends Exact<DeepPartial<GetOnlyMethodRequest>, I>>(object: I): GetOnlyMethodRequest {
const message = { ...baseGetOnlyMethodRequest } as GetOnlyMethodRequest;
message.id = object.id ?? '';
return message;
Expand Down Expand Up @@ -407,7 +409,7 @@ export const GetOnlyMethodResponse = {
return obj;
},

fromPartial(object: DeepPartial<GetOnlyMethodResponse>): GetOnlyMethodResponse {
fromPartial<I extends Exact<DeepPartial<GetOnlyMethodResponse>, I>>(object: I): GetOnlyMethodResponse {
const message = { ...baseGetOnlyMethodResponse } as GetOnlyMethodResponse;
message.entity =
object.entity !== undefined && object.entity !== null ? Entity.fromPartial(object.entity) : undefined;
Expand Down Expand Up @@ -455,7 +457,7 @@ export const WriteMethodRequest = {
return obj;
},

fromPartial(object: DeepPartial<WriteMethodRequest>): WriteMethodRequest {
fromPartial<I extends Exact<DeepPartial<WriteMethodRequest>, I>>(object: I): WriteMethodRequest {
const message = { ...baseWriteMethodRequest } as WriteMethodRequest;
message.id = object.id ?? '';
return message;
Expand Down Expand Up @@ -494,7 +496,7 @@ export const WriteMethodResponse = {
return obj;
},

fromPartial(_: DeepPartial<WriteMethodResponse>): WriteMethodResponse {
fromPartial<I extends Exact<DeepPartial<WriteMethodResponse>, I>>(_: I): WriteMethodResponse {
const message = { ...baseWriteMethodResponse } as WriteMethodResponse;
return message;
},
Expand Down Expand Up @@ -548,7 +550,7 @@ export const Entity = {
return obj;
},

fromPartial(object: DeepPartial<Entity>): Entity {
fromPartial<I extends Exact<DeepPartial<Entity>, I>>(object: I): Entity {
const message = { ...baseEntity } as Entity;
message.id = object.id ?? '';
message.name = object.name ?? '';
Expand Down Expand Up @@ -604,6 +606,7 @@ interface Rpc {
}

type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;

export type DeepPartial<T> = T extends Builtin
? T
: T extends Array<infer U>
Expand All @@ -614,6 +617,11 @@ export type DeepPartial<T> = T extends Builtin
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;

type KeysOfUnion<T> = T extends T ? keyof T : never;
export type Exact<P, I extends P> = P extends Builtin
? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & Record<Exclude<keyof I, KeysOfUnion<P>>, never>;

// If you get a compile-error about 'Constructor<Long> and ... have no overlap',
// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'.
if (util.Long !== Long) {
Expand Down
Loading

0 comments on commit 808f8a7

Please sign in to comment.