foo
variable should keep { a: { b: string } }
inferred type while it's restricted to conform another type, Foo
. It preferably should have type { a: { b: string } } & Foo
:
type Foo = { [k: string]: { [k: string]: string } };
const _foo = { a: { b: 'c' } };
export const foo: typeof _foo & Foo = _foo;
But it's acceptable for it to have { a: { b: string } }
type - as long as it produces type error in case it doesn't conform to Foo
:
type Foo = { [k: string]: { [k: string]: string } };
function checkFoo(foo: Foo) {};
let foo = { a: { b: 'c' } };
checkFoo(foo);
The objective is to make TypeScript emit only single JavaScript line:
var foo = { a: { b: 'c' } };
checkFoo
function can also present in compiler output, as long as it's not called, so it could be removed by a minifier as dead code.
I'd prefer to avoid unnecessary compiler output like checkFoo(foo)
if possible.
What are the options here? Are there ones that are specific to recent TypeScript versions, 2.7 and 2.8?
While I don't necessarily agree with the premise of avoiding the extra function call at all cost, we can generate a compiler error that emits no JS code if foo
does not implement the expected interface without using functions.
The approach is based on creating a conditional type (available in 2.8) that returns different string literal types based on whether the type of the constant implements the interface or not. Then we can use this type in a place where we expect a specific type literal.
To get an error, we will need to use the type in a way that generates no code, and I found 2 possible ways, either in a declaration, or in a type alias.
export type Foo = { [k: string]: { [k: string]: string } };
type TestType<T, TBase> = T extends TBase ? "OK" : "Implementation does not extend expected type";
type Validate<T extends "OK"> = "OK";
let foo = { a: { b: 'c' } };
// use type in a constant
declare const fooTest : Validate<TestType<typeof foo, Foo>> // ok
// use type in a type alias
type fooTest = Validate<TestType<typeof foo, Foo>> //ok
let fooBad = { a: { b: 10 } };
declare const fooBadTest : Validate<TestType<typeof fooBad, Foo>>; // error: Type '"Implementation does not extend expected type"' does not satisfy the constraint '"OK"'
type fooBadTest = Validate<TestType<typeof fooBad, Foo>> // error: Type '"Implementation does not extend expected type"' does not satisfy the constraint '"OK"'.
One problem with this approach is that we have to introduce extra type aliases/ constant names that pollute the namespace.
Playground link.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With