kaleidawave / ezno

A JavaScript compiler and TypeScript checker written in Rust with a focus on static analysis and runtime performance
https://kaleidawave.github.io/posts/introducing-ezno/
MIT License
2.41k stars 45 forks source link

Assigning excess properties can break type system #177

Open kaleidawave opened 1 month ago

kaleidawave commented 1 month ago

The following with ?: passes through the type system (at least will after #122) but throws at runtime

function func(param: { prop: string }) {
    const v1: { excess?: number } = param;
    const v2: number = v1.excess ?? 0;
    return Math.sin(v2)
}

func({ prop: "", excess: console })

This is also true for union types

function func(param: { prop: string }) {
    const v1: {} | { excess: number } = param;
    const v2: number = v1.excess ?? 0;
    return Math.sin(v2)
}

func({ prop: "", excess: console })

The problem is sort of related to narrowing because the v1.excess ?? performs narrowing (number | undefined -> number). Maybe narrowing checks could be added during event application but would like to avoid that. (I should raise a related issue around getter property narrowing)


The Exclusive<...> helper type (to be added in #157) can help here (although that is opt in and not in TSC). Maybe a mode could enforce this for assigning to unions and conditional properties. Want to not ignore this issue while also not creating something that is too strict (and creates a lot of breaking changes).

kaleidawave commented 1 month ago

Note this is also an issue in TSC (https://github.com/microsoft/TypeScript/issues/12936).

Some cases are currently caught by ezno-checker because objects are treated dependently (for variables: it separates assignment restrictions from current values)

const x: {} = { b: "hi" }
const y: { b?: number } = x;
y.b satisfies boolean; // fails with = "hi"

(+ because of assignment tracking via events)

const x: { b: string } = { b: "hi" };
const y: {} = x;
const z: { b?: number } = y;
(function () { z.b = 5; })();
x.b satisfies string; // fails with = number

The issue is around what happens when we don't have the dependent types.

RobertSandiford commented 1 month ago

See my comment on TS Exact types here: https://github.com/microsoft/TypeScript/issues/12936#issuecomment-2198567348

A value that is structurally typed (may have excess properties) cannot be safely assigned to a type that includes an optional property that is not present on the type of the value. Only an exact typed object or a fresh object (that we have full information on) can be assigned to a type with an additional optional property. I talked about a type that would work this was called a noextend type.

Spoken from a TS perspective - I'm not familiar with the workings of ezno. If I was making a rational type system I would make basic types work the noextend way instead of implementing them the TS way.

kaleidawave commented 1 month ago

See my comment on TS Exact types here: microsoft/TypeScript#12936 (comment)

The link shoes a good example for a fail that can occur.

With any solution to this I would like to make it somewhat TSC compatible. Technically from subtyping rules excess properties are fine, the problem is that TSC removes information (etc the extra properties) for variable type annotations.

With the more dependent type system that is aware of mutations through side effects, your example should raise an issue already (or very soon from #157) from ezno check.

Maybe there could be a flag so that all user code is subjected to all type object type annotations being treated as the upcoming Exclusive helper will work?