4

I need to test each value of multiple HTMLInputElements using a/some test function(s), and auto-generate a nested list of checkboxes for each, with the conditions which are expressed as Conditions below:

type TestFunction = (value: string) => boolean
type TestFunctionObject = { [description: string]: Conditions }
type Conditions = TestFunction | TestFunctionObject

So, for example, if I have:

const conds1: Conditions = {
  "Consists of alphanumerics": /^[a-zA-Z0-9]$/.test
}

I get a checkbox labeled as "Consists of alphanumerics". And if:

const conds2: Conditions = /^[a-zA-Z0-9]$/.test

I don't want a checkbox but just validate with that.

Auto-generating is done without any problem. Then, I wrote a type which represents validity of each TestFunction:

type Validity<C extends Conditions> = C extends TestFunction
  ? boolean
  : { [K in keyof C]: Validity<C[K]> }

Now I got an error from TS on C[K]; playground here. It says Type 'Conditions[K]' is not assignable to type 'TestFunctionObject'. Type conditioning doesn't seem to narrow Conditions to just TestFunctionObject.

How can I get it to work?

Addition for jcalz's answer: Playground with examples

1 Answer 1

5

I don't think the compiler does a lot of narrowing within the else clause of conditional types (after :) the way it does with values via control flow analysis. So while it's obvious to you that in the conditional type C extends TestFunction, the C in the else clause should extend TestFunctionObject, the compiler doesn't realize it.

But the compiler does do narrowing within the then clause (between ? and :), so the easiest fix for this is to add another conditional type:

type Validity<C extends Conditions> = C extends TestFunction ? boolean
  : C extends TestFunctionObject ? { [K in keyof C]: Validity<C[K]> } : never

Note that the last conditional type has never as the else clause. That's a common idiom with conditional types; sometimes you know the else clause cannot be reached; and in the absence of an invalid type, the never type is a good alternative.

Or, since you weren't doing much with the then clause to begin with, flip the clauses of your original check:

type Validity<C extends Conditions> = C extends TestFunctionObject ?
  { [K in keyof C]: Validity<C[K]> } : boolean

Either should work. Hope that helps; good luck!

Sign up to request clarification or add additional context in comments.

4 Comments

If I am not mistaken, the existence of narrowing in the true branch (and not the false branch) is what Anders is implying when he writes, "... references to T within X have an additional type parameter constraint U." Is that your interpretation of that sentence too? github.com/Microsoft/TypeScript/pull/21316 I haven't seen this behavior documented anywhere else.
Yes, I agree. I see the same line in the "what's new" description of conditional types but it's buried within the distributive conditional type section.
Wow, I'd been seeing a cheatsheet from its back! 🤣 Thanks, the error disappears. But actually it doesn't seem to work recursively. Added a playground. Is this a different problem?
That problem is just with how the type is displayed via IntelliSense, not with the actual type. See Microsoft/TypeScript#23897 for the display bug. Note that in your Playground example, validity3["Include at least"]["1 basic Latin capital letter"] is of type boolean but validity3["Include at least"].rando is a compiler error (with --noImplicitAny turned on)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.