There's a lot of considerations and trade-offs when implementing your own type system. Do you want the type system to be 100% sound, or do you want it to be convenient to use?
This comes up regularly, and recently did so again when implementing variance of functions with optional parameters. Essentially, I opted for convenience over soundness. Consider the following TS which will crash at runtime, but is permitted by TS' type system:
type NoParams = () => void
type OptionalStringParam = (a?: string) => void
type OptionalNumberParam = (a?: number) => void
let noParams: NoParams = () => {}
let optionalStringParam: OptionalStringParam = str => console.log(str?.toUpperCase() || "Nothing")
let optionalNumberParam: OptionalNumberParam = num => console.log(num !== undefined ? (num / 2) : "Nothing")
noParams = optionalStringParam
optionalNumberParam = noParams
optionalNumberParam(2)
Basically, `noParams = optionalStringParam` probably shouldn't be allowed. But in the general case, it is certainly convenient. So I opted for the same in my own type system.
This comes up regularly, and recently did so again when implementing variance of functions with optional parameters. Essentially, I opted for convenience over soundness. Consider the following TS which will crash at runtime, but is permitted by TS' type system:
Basically, `noParams = optionalStringParam` probably shouldn't be allowed. But in the general case, it is certainly convenient. So I opted for the same in my own type system.