Simple question re a function that returns a type:

Trying to create simple "factory" functions that make a type based on some other type. This works fine:
const makeBar = <T1, T2>(inputs: Type<T1>, outputs: Type<T2>) => type({
weird: "boolean",
in: inputs,
out: outputs
})

const Custom1 = type({
bar: "number"
})

const Custom2 = type({
foo: "string.integer"
})

const Other = makeBar(Custom1, Custom2)
const makeBar = <T1, T2>(inputs: Type<T1>, outputs: Type<T2>) => type({
weird: "boolean",
in: inputs,
out: outputs
})

const Custom1 = type({
bar: "number"
})

const Custom2 = type({
foo: "string.integer"
})

const Other = makeBar(Custom1, Custom2)
But if it's "string.integer.parse" it fails to compile. Any hint as to why? Do I have a problem with my makeBar() prototype?
16 Replies
TizzySaurus
TizzySaurus2d ago
Wdym by "fails to compile"? Do you get a runtime error? If so, what's the error?
Mike Radford
Mike RadfordOP2d ago
Compilation error:
error TS2345: Argument of type 'Type<{ foo: (In: string) => To<number>; }, {}>' is not assignable to parameter of type 'Type<{ foo: string; }, {}>'.
The types of 'readonly().map' are incompatible between these types.
Type '<transformed extends listable<MappedTypeProp>, r = Type<{ [k in keyof intersectUnion<{ [k in keyof ({ [prop in (transformed extends array ? transformed : [transformed])[number] as Extract<applyHomomorphicOptionality<{ readonly foo: (In: string) => To<...>; }, prop>, { ...; }>["key"]]: prop["value"][" arkInferred"]; ...' is not assignable to type '<transformed extends listable<MappedTypeProp>, r = Type<{ [k in keyof intersectUnion<{ [k in keyof ({ [prop in (transformed extends array ? transformed : [transformed])[number] as Extract<applyHomomorphicOptionality<{ readonly foo: string; }, prop>, { ...; }>["key"]]: prop["value"][" arkInferred"]; } & { [prop in (t...'.
Types of parameters 'flatMapEntry' and 'flatMapEntry' are incompatible.
Types of parameters 'entry' and 'entry' are incompatible.
Type 'BaseTypeProp<"required", "foo", (In: string) => To<number>, {}>' is not assignable to type 'BaseTypeProp<"required", "foo", string, {}>'.
Type '(In: string) => To<number>' is not assignable to type 'string'.
error TS2345: Argument of type 'Type<{ foo: (In: string) => To<number>; }, {}>' is not assignable to parameter of type 'Type<{ foo: string; }, {}>'.
The types of 'readonly().map' are incompatible between these types.
Type '<transformed extends listable<MappedTypeProp>, r = Type<{ [k in keyof intersectUnion<{ [k in keyof ({ [prop in (transformed extends array ? transformed : [transformed])[number] as Extract<applyHomomorphicOptionality<{ readonly foo: (In: string) => To<...>; }, prop>, { ...; }>["key"]]: prop["value"][" arkInferred"]; ...' is not assignable to type '<transformed extends listable<MappedTypeProp>, r = Type<{ [k in keyof intersectUnion<{ [k in keyof ({ [prop in (transformed extends array ? transformed : [transformed])[number] as Extract<applyHomomorphicOptionality<{ readonly foo: string; }, prop>, { ...; }>["key"]]: prop["value"][" arkInferred"]; } & { [prop in (t...'.
Types of parameters 'flatMapEntry' and 'flatMapEntry' are incompatible.
Types of parameters 'entry' and 'entry' are incompatible.
Type 'BaseTypeProp<"required", "foo", (In: string) => To<number>, {}>' is not assignable to type 'BaseTypeProp<"required", "foo", string, {}>'.
Type '(In: string) => To<number>' is not assignable to type 'string'.
TizzySaurus
TizzySaurus2d ago
Does
<T1 extends type.Any, T2 extends type.Any>(inputs: T1, outputs: T2) => ...
<T1 extends type.Any, T2 extends type.Any>(inputs: T1, outputs: T2) => ...
work?
Mike Radford
Mike RadfordOP2d ago
that doesn't even compile without the .parse :
src/test.ts:14:3 - error TS2322: Type 'T1' is not assignable to type 'validateProperty<T1, "required", {}, bindThis<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }>>'.
Type 'Any<any, any>' is not assignable to type 'validateProperty<T1, "required", {}, bindThis<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }>>'.

14 in: inputs,
~~

src/test.ts:14:3
14 in: inputs,
~~~~~~~~~~
The expected type comes from property 'in' which is declared here on type 'validateObjectLiteral<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }, {}, bindThis<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }>>'

src/test.ts:15:3 - error TS2322: Type 'T2' is not assignable to type 'validateProperty<T2, "required", {}, bindThis<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }>>'.
Type 'Any<any, any>' is not assignable to type 'validateProperty<T2, "required", {}, bindThis<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }>>'.

15 out: outputs
~~~

src/test.ts:15:3
15 out: outputs
~~~~~~~~~~~~
The expected type comes from property 'out' which is declared here on type 'validateObjectLiteral<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }, {}, bindThis<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }>>'
src/test.ts:14:3 - error TS2322: Type 'T1' is not assignable to type 'validateProperty<T1, "required", {}, bindThis<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }>>'.
Type 'Any<any, any>' is not assignable to type 'validateProperty<T1, "required", {}, bindThis<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }>>'.

14 in: inputs,
~~

src/test.ts:14:3
14 in: inputs,
~~~~~~~~~~
The expected type comes from property 'in' which is declared here on type 'validateObjectLiteral<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }, {}, bindThis<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }>>'

src/test.ts:15:3 - error TS2322: Type 'T2' is not assignable to type 'validateProperty<T2, "required", {}, bindThis<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }>>'.
Type 'Any<any, any>' is not assignable to type 'validateProperty<T2, "required", {}, bindThis<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }>>'.

15 out: outputs
~~~

src/test.ts:15:3
15 out: outputs
~~~~~~~~~~~~
The expected type comes from property 'out' which is declared here on type 'validateObjectLiteral<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }, {}, bindThis<{ readonly weird: "boolean"; readonly in: T1; readonly out: T2; }>>'
Is this a common pattern to have a function that takes a type and returns a new type?
TizzySaurus
TizzySaurus2d ago
I'd say so, yeah. There's even an example of it on the docs: https://arktype.io/docs/generics#external
ArkType
ArkType Docs
TypeScript's 1:1 validator, optimized from editor to runtime
Mike Radford
Mike RadfordOP2d ago
interesting ok not sure how I missed it.
TizzySaurus
TizzySaurus2d ago
Does this work if you explicitly type the return? I.e. Type<{weird: boolean, in: T1, out: T2}>
Mike Radford
Mike RadfordOP2d ago
worth noting that just going one additional level also break compilation:
const makeFoo = <T1, T2>(inputs: Type<T1>, outputs: Type<T2>) => type({
in: inputs,
out: outputs
})

const makeBar = <T1, T2>(inputs: Type<T1>, outputs: Type<T2>) => type({
weird: "boolean",
in: inputs,
out: outputs
foo: makeFoo(inputs, outputs) // <-- this line gets an error
})
const makeFoo = <T1, T2>(inputs: Type<T1>, outputs: Type<T2>) => type({
in: inputs,
out: outputs
})

const makeBar = <T1, T2>(inputs: Type<T1>, outputs: Type<T2>) => type({
weird: "boolean",
in: inputs,
out: outputs
foo: makeFoo(inputs, outputs) // <-- this line gets an error
})
Let me try I don't really know how to get that type
TizzySaurus
TizzySaurus2d ago
As in
const makeFoo = <...>(inputs: T1, outputs: T2): Type<{weird: boolean, in: T1, out: T2}> => ...
const makeFoo = <...>(inputs: T1, outputs: T2): Type<{weird: boolean, in: T1, out: T2}> => ...
Just curious if that makes it work
Mike Radford
Mike RadfordOP2d ago
oh I see it works in the string.integer setup, but adding .parse still breaks compilation
TizzySaurus
TizzySaurus2d ago
Yeah, not sure then I'm afraid. Maybe @ssalbdivad can give some insight
ssalbdivad
ssalbdivad2d ago
Hmm I'm really not sure why this comparison is even happening- seems like a possible TS bug? Maybe @Andarist would have some insight from that perspective. A workaround is to accept type.cast instead, which is basically "anything arktype recognizes as inferred like X" without checking all the type methods etc. which simplifies the inference and seems to work here:
import { type } from "arktype"

const makeBar = <T1, T2>(inputs: type.cast<T1>, outputs: type.cast<T2>) =>
type({
weird: "boolean",
in: inputs,
out: outputs
})

const Custom1 = type({
bar: "number"
})

const Custom2 = type({
foo: "string.integer.parse"
})

const Other = makeBar(Custom1, Custom2)
import { type } from "arktype"

const makeBar = <T1, T2>(inputs: type.cast<T1>, outputs: type.cast<T2>) =>
type({
weird: "boolean",
in: inputs,
out: outputs
})

const Custom1 = type({
bar: "number"
})

const Custom2 = type({
foo: "string.integer.parse"
})

const Other = makeBar(Custom1, Custom2)
Andarist
Andarist2d ago
looks buggy to me but who knows
Andarist
Andaristthis hour
usually i'd say the return type inference is at fault here but explicit type arguments turn off inference altogether so 🤷‍♂️ as always - I could investigate a small repro case of this 😛 fwiw, it's ur weird : r extends infer _ ? _ : never return type that breaks this and ofc a regular NoInfer fixes this issue:
diff --git a/ark/type/type.ts b/ark/type/type.ts
index c2cb322ff..149845cc2 100644
--- a/ark/type/type.ts
+++ b/ark/type/type.ts
@@ -63,7 +63,7 @@ export interface TypeParser<$ = {}> extends Ark.boundTypeAttachments<$> {
// Parse and check the definition, returning either the original input for a
// valid definition or a string representing an error message.
def: type.validate<def, $>
- ): r extends infer _ ? _ : never
+ ): NoInfer<r>

/**
* Create a {@link Generic} from a parameter string and body definition.
diff --git a/ark/type/type.ts b/ark/type/type.ts
index c2cb322ff..149845cc2 100644
--- a/ark/type/type.ts
+++ b/ark/type/type.ts
@@ -63,7 +63,7 @@ export interface TypeParser<$ = {}> extends Ark.boundTypeAttachments<$> {
// Parse and check the definition, returning either the original input for a
// valid definition or a string representing an error message.
def: type.validate<def, $>
- ): r extends infer _ ? _ : never
+ ): NoInfer<r>

/**
* Create a {@link Generic} from a parameter string and body definition.
Mike Radford
Mike RadfordOP20h ago
nice! does this address the original issue? I just updated the playground link above to include the other line that fails to compile.

Did you find this page helpful?