Skip to content

Instantly share code, notes, and snippets.

@marcogrcr
Last active July 18, 2023 20:43
Show Gist options
  • Save marcogrcr/344b32d3925947c96ba99c7a4fb7aaa2 to your computer and use it in GitHub Desktop.
Save marcogrcr/344b32d3925947c96ba99c7a4fb7aaa2 to your computer and use it in GitHub Desktop.
TypeScript unexpected type checks
// Even with the strictest settings, the TypeScript compiler may have looser type checking than what you would expect.
interface Data {
req1: number;
req2: number;
opt1?: number;
opt2?: number;
}
function logData(data: Data): void {
console.log(data);
}
//
// IMPLICIT TYPE DECLARATION
// As expected, required properties are checked:
// > Argument of type '{}' is not assignable to parameter of type 'Data'.
// > Type '{}' is missing the following properties from type 'Data': req1, req2 (2345)
logData({});
// As expected, partial required properties are not enough:
// > Argument of type '{ req1: number; }' is not assignable to parameter of type 'Data'.
// > Property 'req2' is missing in type '{ req1: number; }' but required in type 'Data'. (2345)
logData({ req1: 1 });
// Even if required properties are present, extraneous properties are not allowed:
// > Argument of type '{ req1: number; req2: number; opt3: number; }' is not assignable to parameter of type 'Data'.
// > Object literal may only specify known properties, and 'opt3' does not exist in type 'Data'. (2345)
logData({ req1: 1, req2: 2, opt3: 3 });
//
// EXPLICIT TYPE DECLARATION
// The same applies in const/let/var declarations
// > Type '{}' is missing the following properties from type 'Data': req1, req2 (2739)
var d1: Data = {};
let d2: Data = {};
const d3: Data = {};
// > Property 'req2' is missing in type '{ req1: number; }' but required in type 'Data'. (2741)
var d4: Data = { req1: 1 };
let d5: Data = { req1: 1 };
const d6: Data = { req1: 1 };
// > Type '{ req1: number; req2: number; opt3: number; }' is not assignable to type 'Data'.
// > Object literal may only specify known properties, and 'opt3' does not exist in type 'Data'.(2322)
var d7: Data = { req1: 1, req2: 2, opt3: 3 };
let d8: Data = { req1: 1, req2: 2, opt3: 3 };
const d9: Data = { req1: 1, req2: 2, opt3: 3 };
//
// IMPLICIT CASTING
// Due to TypeScript's "duck typing", it will happily allow extraneous properties as long as it's first another type and it overlaps on all required properties.
// This can be a source of bugs when there are typos on optional properties.
// This can be avoided with implicit or explicit type declaration, as shown on the previous examples.
const d10 = { req1: 1, req2: 2, opt3: 3 };
logData(d10);
//
// EXPLICIT CASTING
// Explicit casting, will disallow types with extraneous properties if there's no overlap on all required properties.
// > Conversion of type '{ opt3: number; }' to type 'Data' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
// > Type '{ opt3: number; }' is missing the following properties from type 'Data': req1, req2 (2352)
logData({ opt3: 3 } as Data);
// > Conversion of type '{ req1: number; opt3: number; }' to type 'Data' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
// > Property 'req2' is missing in type '{ req1: number; opt3: number; }' but required in type 'Data'.(2352)
logData({ req1: 1, opt3: 3 } as Data);
// However, explicit casting will happily allow extraneous properties if all the required properties are present
// Just like with "duck typing", this can be a source of bugs when there are typos on optional properties.
// This can be avoided by removing the explicit casting if possible.
logData({ req1: 1, req2: 2, opt3: 3 } as Data);
// Finally, explicit casting will happily accept objects with partial or no overlap in the required properties, as long as there are no extraneous properties
// This can be a source of bugs and unexpected `TypeError: Cannot read properties of undefined` given the expectation that required properties will always have a value.
// This can be avoided by removing the explicit casting if possible.
logData({} as Data);
logData({ req1: 1 } as Data);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment