#How to let typescript help me narrow a generic to a specific type in switch cases?

1 messages · Page 1 of 1 (latest)

eternal crane
#

I'm writing a function like this but got 2 errors. Why doesn't typescript infer that in case "a", T should be a specific subtype of A which is a

type A = "a" | "b";
type AA = {};

type B<T extends A> = T extends A ? AA : never;
function foo <T extends A>(a: A): B<T> {
  switch (a ) {
    case "a": {
      let aa : T = "a"; // Type '"a"' is not assignable to type 'T'. '"a"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'A'. 
      return bar(); // Type 'AA' is not assignable to type 'B<T>'.
    }
  }
}

function bar (): AA {
  return {};
}
#

How to let typescript help me narrow to a generic to a specific type in switch cases?

#

How to let typescript help me narrow a generic to a specific type in switch cases?

barren cipher
#

it's silly in that case because "a" happens to be a unit type. but consider an example like this:

type A = string | number

function foo<T extends A>(a: T): T {
  switch (typeof a) {
    case "string":
      let aa: T = "some string"
      return aa
  }
  return a
}

const hmm = foo<"a different string">("a different string")
//    ^? const hmm: "a different string"
console.log(hmm) // will log "some string"
#

that example would smuggle out "some string" and have it be typed as "a different string" if typescript didn't yell at you about expecting "some string" to be assignable to T

eternal crane
barren cipher
#

yes. the caller gets to specify T and the function implementation has to be valid for all possible types T, but that's not the case in the above example

eternal crane
#

how about the second error? can typescript infer in case "a" the return type should be AA ?

barren cipher
#

no, similar problems can happen there depending on what B looks like

#

what does your real code look like? i might have some suggestions for how you can structure things

eternal crane
#

yeah!

#

I am trying to write a function to check bowser's compatibility

export const FEATURE_WEBP = 'FEATURE_WEBP';
export type FeatureWebp = typeof FEATURE_WEBP;
export type FeatureWebpOptions = {
  lossy?: boolean;
  lossyless?: boolean;
  alpha?: boolean;
  animation?: boolean;
};
export type FeatureWebpCheckResult = FeatureWebpOptions;

export type Features = typeof features[number];
export type Options<T extends Features> = {
  persist?: boolean;
} & T extends FeatureWebp
  ? FeatureWebpOptions
  : never;
export type Result<T extends Features> = T extends FeatureWebp
  ? FeatureWebpOptions
  : never;

export const features = [FEATURE_WEBP] as const;

export async function checkFeature<T extends Features>(
  feature: T,
  options?: Options<T>,
): Promise<Result<T> | null> {
  switch (feature) {
    case FEATURE_WEBP: {
      return await checkWebp(options);
    }
    default:
      return null;
  }
}

export async function checkWebp(
  options: FeatureWebpOptions = {},
): Promise<FeatureWebpCheckResult> {
  const result: FeatureWebpCheckResult = {};
  const testImages = {
    lossy: '',
    lossless: '',
    alpha: "",
    animation: "",
  } as const;
  const testImageRender = (data: string): Promise<boolean> => {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = function () {
        const result = img.width > 0 && img.height > 0;
        resolve(result);
      };
      img.onerror = function (error) {
        reject(error);
      };
      img.src = `data:image/webp;base64,${data}`;
    });
  };

  switch (true) {
    case options.lossy: {
      result.lossy = await testImageRender(testImages.lossless);
    }
    case options.alpha: {
      result.alpha = await testImageRender(testImages.alpha);
    }
    default:
      break;
  }

  return result;
}

barren cipher
#

overloading would probably be the least-painful way to handle this:

clear mortarBOT
#
mkantor#7432

Preview:ts ... export async function checkFeature( feature: FeatureWebp, options?: Options<FeatureWebp> ): Promise<Result<FeatureWebp> | null> export async function checkFeature( feature: Features, options?: Options<Features> ): Promise<Result<Features> | null> { switch (feature) { case FEATURE_WEBP: { return await checkWebp(options) } default: return null } } ...