Skip to content

.min or .destruct on .constructed fall back to main type #79

@phryneas

Description

@phryneas

Hey there again, sorry that I keep digging up weird stuff :)
I have created a constructed type with

const stringOrObjectWithKey = string.construct(
  (arg: { key: string } | string) =>
    typeof arg === "string" ? [arg] : [arg.key]
);

and I can call that just fine like

stringOrObjectWithKey("asd");
stringOrObjectWithKey({ key: "asd" });

now, when I chain that like

const withMin = stringOrObjectWithKey.min(1);

I lose the ability to call it with the overload:

withMin("asd");
// Argument of type '{ key: string; }' is not assignable to parameter of type 'string'.ts(2345)
withMin({ key: "asd" });

Looking at the types, I don't really see what is wrong there - and to be honest, these types are quite complicated to get started with so I'm a bit hesitant to do a PR on this.

I have, though, created a type test for this that you can use to verify this behaviour:
CodeSandBox

I hope that helps to pinpoint it down.

Please don't hesitate to contact me if I can help any further 🙂

/*eslint-disable @typescript-eslint/no-unused-vars */

import Schema, { string } from "computed-types";
import { SchemaResolveType, SchemaInput } from "computed-types/lib/schema/io";

function expectType<T>(t: T) {
  return t;
}
function expectAssignable<Subtype, _Type extends Subtype>() {}

const stringOrObjectWithKey = string.construct(
  (arg: { key: string } | string) =>
    typeof arg === "string" ? [arg] : [arg.key]
);

stringOrObjectWithKey("asd");
stringOrObjectWithKey({ key: "asd" });
expectType<(v: string | { key: string }) => string>(stringOrObjectWithKey);
expectAssignable<
  string | { key: string },
  SchemaInput<typeof stringOrObjectWithKey>
>();
expectAssignable<
  SchemaInput<typeof stringOrObjectWithKey>,
  string | { key: string }
>();

const destructed = stringOrObjectWithKey.destruct();
destructed("asd");
destructed({ key: "asd" });
expectType<(v: string | { key: string }) => [any, string?]>(destructed);
// second signature missing here
expectType<(v: string) => [any, string?]>(destructed);
expectAssignable<string | { key: string }, SchemaInput<typeof destructed>>();
expectAssignable<SchemaInput<typeof destructed>, string | { key: string }>();
expectAssignable<[any, string?], SchemaResolveType<typeof destructed>>();
expectAssignable<SchemaResolveType<typeof destructed>, [any, string?]>();

const withMin = stringOrObjectWithKey.min(1);
withMin("asd");
withMin({ key: "asd" });
expectType<(v: string | { key: string }) => string>(withMin);
// second signature missing here
expectType<(v: string) => string>(withMin);
expectAssignable<string | { key: string }, SchemaInput<typeof withMin>>();
expectAssignable<SchemaInput<typeof withMin>, string | { key: string }>();
expectAssignable<string, SchemaResolveType<typeof withMin>>();
expectAssignable<SchemaResolveType<typeof withMin>, string>();

const inSchema = Schema({ test: stringOrObjectWithKey });
inSchema({ test: "asd" });
inSchema({ test: { key: "asd" } });
expectType<(v: { test: string | { key: string } }) => { test: string }>(
  inSchema
);
expectAssignable<
  { test: string | { key: string } },
  SchemaInput<typeof inSchema>
>();
expectAssignable<
  SchemaInput<typeof inSchema>,
  { test: string | { key: string } }
>();

const destructedSchema = inSchema.destruct();
destructedSchema({ test: "asd" });
destructedSchema({ test: { key: "asd" } });
expectType<(v: { test: string | { key: string } }) => [any, { test: string }?]>(
  destructedSchema
);

const mergedSchema = Schema.merge({ other: string.optional() }, inSchema);
mergedSchema({ test: "asd" });
mergedSchema({ test: { key: "asd" } });
expectType<(v: { test: string | { key: string } }) => { test: string }>(
  mergedSchema
);
expectAssignable<
  { test: string | { key: string } },
  SchemaInput<typeof mergedSchema>
>();
expectAssignable<
  SchemaInput<typeof mergedSchema>,
  { test: string | { key: string } }
>();

const mergedDestructedSchema = mergedSchema.destruct();
mergedDestructedSchema({ test: "asd" });
mergedDestructedSchema({ test: { key: "asd" } });
expectType<(v: { test: string | { key: string } }) => [any, { test: string }?]>(
  mergedDestructedSchema
);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions