TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

New `--enforceReadonly` compiler option to enforce read-only semantics in type relations

Open ahejlsberg opened this issue 1 year ago • 45 comments

This PR introduces a new --enforceReadonly compiler option to enforce read-only semantics in type relations. Currently, the readonly modifier prohibits assignments to properties so marked, but it still considers a readonly property to be a match for a mutable property in subtyping and assignability type relationships. With this PR, readonly properties are required to remain readonly across type relationships:

type Box<T> = { value: T };
type ImmutableBox<T> = { readonly value: T };

const immutable: ImmutableBox<string> = { value: "hello" };
const mutable: Box<string> = immutable;  // Error with --enforceReadonly
mutable.value = "ouch!";

When compiled with --enforceReadonly the assignment of immutable to mutable becomes an error because the value property is readonly in the source type but not in the target type.

The --enforceReadonly option also validates that derived classes and interfaces don't violate inherited readonly constraints. For example, an error is reported in the example below because it isn't possible for a derived class to remove mutability from an inherited property (a getter-only declaration is effectively readonly, yet assignments are allowed when treating an instance as the base type).

interface Base {
    x: number;
}

interface Derived extends Base {  // Error with --enforceReadonly
    get x(): number;
}

In type relationships involving generic mapped types, --enforceReadonly ensures that properties of the target type are not more mutable than properties of the source type:

type Mutable<T> = { -readonly [P in keyof T]: T[P] };

function foo<T>(mt: Mutable<T>, tt: T, rt: Readonly<T>) {
    mt = tt;  // Error with --enforceReadonly
    mt = rt;  // Error with --enforceReadonly
    tt = mt;
    tt = rt;  // Error with --enforceReadonly
    rt = mt;
    rt = tt;
}

The --enforceReadonly option slightly modifies the effect of as const assertions and const type parameters to mean "as const as possible" without violating constraints. For example, the following compiles successfully with --enforceReadonly:

const obj: { a: string, b: number } = { a: "hello", b: 42 } as const;

whereas the following does not:

const c = { a: "hello", b: 42 } as const;  // { readonly a: "hello", readonly b: 42 }
const obj: { a: string, b: number } = c;  // Error

Some examples using const type parameters:

declare function f10<T>(obj: T): T;
declare function f11<const T>(obj: T): T;
declare function f12<const T extends { a: string, b: number }>(obj: T): T;
declare function f13<const T extends { a: string, readonly b: number }>(obj: T): T;
declare function f14<const T extends Record<string, unknown>>(obj: T): T;
declare function f15<const T extends Readonly<Record<string, unknown>>>(obj: T): T;

f10({ a: "hello", b: 42 });  // { a: string; b: number; }
f11({ a: "hello", b: 42 });  // { readonly a: "hello"; readonly b: 42; }
f12({ a: "hello", b: 42 });  // { a: "hello"; b: 42; }
f13({ a: "hello", b: 42 });  // { a: "hello"; readonly b: 42; }
f14({ a: "hello", b: 42 });  // { a: "hello"; b: 42; }
f15({ a: "hello", b: 42 });  // { readonly a: "hello"; readonly b: 42; }

Stricter enforcement of readonly has been debated ever since the modifier was introduced eight years ago in #6532. Our rationale for the current design is outlined here. Given the huge body of existing type definitions that were written without deeper consideration of read-only vs. read-write semantics, it would be a significant breaking change to strictly enforce readonly semantics across type relationships. For this reason, the --enforceReadonly compiler option defaults to false. However, by introducing the option now, it becomes possible to gradually update code bases to correct readonly semantics in anticipation of the option possibly becoming part of the --strict family in the future.

Fixes #13347.

ahejlsberg avatar Apr 23 '24 18:04 ahejlsberg

Looks like you're introducing a change to the public API surface area. If this includes breaking changes, please document them on our wiki's API Breaking Changes page.

Also, please make sure @DanielRosenwasser and @RyanCavanaugh are aware of the changes, just as a heads up.

typescript-bot avatar Apr 23 '24 18:04 typescript-bot

@typescript-bot test it

ahejlsberg avatar Apr 23 '24 19:04 ahejlsberg

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
test top400 ✅ Started ✅ Results
user test this ✅ Started ✅ Results
run dt ✅ Started ✅ Results
perf test this faster ✅ Started 👀 Results

typescript-bot avatar Apr 23 '24 19:04 typescript-bot

Hey @ahejlsberg, the results of running the DT tests are ready.

Everything looks the same!

You can check the log here.

typescript-bot avatar Apr 23 '24 19:04 typescript-bot

@ahejlsberg Here are the results of running the user tests comparing main and refs/pull/58296/merge:

Everything looks good!

typescript-bot avatar Apr 23 '24 19:04 typescript-bot

@ahejlsberg The results of the perf run you requested are in!

Here they are:

tsc

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-Unions - node (v18.15.0, x64)
Memory used 192,785k (± 0.76%) 193,449k (± 0.94%) ~ 192,136k 195,896k p=0.173 n=6
Parse Time 1.35s (± 1.18%) 1.34s (± 1.67%) ~ 1.32s 1.37s p=0.796 n=6
Bind Time 0.72s (± 0.00%) 0.72s (± 0.00%) ~ 0.72s 0.72s p=1.000 n=6
Check Time 9.60s (± 0.24%) 9.58s (± 0.28%) ~ 9.55s 9.62s p=0.517 n=6
Emit Time 2.63s (± 0.64%) 2.63s (± 0.91%) ~ 2.59s 2.66s p=1.000 n=6
Total Time 14.30s (± 0.24%) 14.28s (± 0.38%) ~ 14.21s 14.33s p=0.421 n=6
angular-1 - node (v18.15.0, x64)
Memory used 1,221,857k (± 0.01%) 1,221,802k (± 0.01%) ~ 1,221,720k 1,221,930k p=0.173 n=6
Parse Time 8.30s (± 0.27%) 8.26s (± 0.25%) -0.04s (- 0.42%) 8.23s 8.29s p=0.035 n=6
Bind Time 2.23s (± 0.18%) 2.23s (± 0.38%) ~ 2.21s 2.23s p=0.115 n=6
Check Time 36.43s (± 0.20%) 36.42s (± 0.32%) ~ 36.26s 36.54s p=1.000 n=6
Emit Time 17.40s (± 1.04%) 17.37s (± 0.32%) ~ 17.28s 17.44s p=0.936 n=6
Total Time 64.35s (± 0.22%) 64.28s (± 0.17%) ~ 64.15s 64.43s p=0.470 n=6
mui-docs - node (v18.15.0, x64)
Memory used 1,753,194k (± 0.00%) 1,753,205k (± 0.00%) ~ 1,753,154k 1,753,270k p=0.873 n=6
Parse Time 10.05s (± 0.73%) 10.02s (± 0.50%) ~ 9.94s 10.07s p=0.628 n=6
Bind Time 3.35s (± 0.78%) 3.37s (± 0.69%) ~ 3.34s 3.41s p=0.196 n=6
Check Time 81.91s (± 0.27%) 82.18s (± 0.21%) +0.27s (+ 0.34%) 81.86s 82.34s p=0.045 n=6
Emit Time 0.19s (± 2.67%) 0.20s (± 2.62%) ~ 0.19s 0.20s p=0.311 n=6
Total Time 95.51s (± 0.22%) 95.77s (± 0.20%) ~ 95.43s 95.94s p=0.054 n=6
self-build-src - node (v18.15.0, x64)
Memory used 2,322,319k (± 0.04%) 2,322,364k (± 0.05%) ~ 2,321,022k 2,323,679k p=1.000 n=6
Parse Time 7.63s (± 0.85%) 7.54s (± 0.56%) -0.09s (- 1.18%) 7.50s 7.59s p=0.045 n=6
Bind Time 2.73s (± 0.78%) 2.72s (± 0.79%) ~ 2.69s 2.75s p=0.468 n=6
Check Time 49.35s (± 0.40%) 49.48s (± 0.70%) ~ 49.16s 50.13s p=0.471 n=6
Emit Time 4.02s (± 2.74%) 4.04s (± 1.59%) ~ 3.95s 4.14s p=0.471 n=6
Total Time 63.73s (± 0.41%) 63.81s (± 0.55%) ~ 63.50s 64.49s p=1.000 n=6
self-build-src-public-api - node (v18.15.0, x64)
Memory used 2,396,698k (± 0.02%) 2,396,771k (± 0.01%) ~ 2,396,518k 2,397,093k p=1.000 n=6
Parse Time 7.75s (± 0.80%) 7.78s (± 0.31%) ~ 7.74s 7.80s p=0.297 n=6
Bind Time 2.44s (± 0.62%) 2.46s (± 0.44%) ~ 2.44s 2.47s p=0.075 n=6
Check Time 50.15s (± 0.41%) 50.26s (± 0.54%) ~ 49.85s 50.66s p=0.378 n=6
Emit Time 3.96s (± 2.75%) 3.96s (± 2.51%) ~ 3.82s 4.12s p=0.689 n=6
Total Time 64.31s (± 0.44%) 64.48s (± 0.45%) ~ 63.93s 64.79s p=0.230 n=6
self-compiler - node (v18.15.0, x64)
Memory used 423,917k (± 0.01%) 423,950k (± 0.01%) ~ 423,901k 424,007k p=0.173 n=6
Parse Time 2.88s (± 1.11%) 2.89s (± 0.69%) ~ 2.86s 2.91s p=0.871 n=6
Bind Time 1.10s (± 0.89%) 1.09s (± 0.47%) -0.01s (- 1.06%) 1.08s 1.09s p=0.039 n=6
Check Time 15.38s (± 0.25%) 15.40s (± 0.46%) ~ 15.29s 15.47s p=0.470 n=6
Emit Time 1.16s (± 1.27%) 1.18s (± 1.99%) ~ 1.14s 1.20s p=0.223 n=6
Total Time 20.53s (± 0.28%) 20.55s (± 0.43%) ~ 20.42s 20.66s p=0.748 n=6
ts-pre-modules - node (v18.15.0, x64)
Memory used 369,266k (± 0.02%) 369,479k (± 0.04%) +213k (+ 0.06%) 369,312k 369,696k p=0.020 n=6
Parse Time 2.45s (± 1.11%) 2.45s (± 0.95%) ~ 2.42s 2.48s p=0.747 n=6
Bind Time 1.32s (± 1.91%) 1.32s (± 1.39%) ~ 1.30s 1.34s p=0.935 n=6
Check Time 13.31s (± 0.14%) 13.35s (± 0.26%) +0.04s (+ 0.31%) 13.29s 13.38s p=0.043 n=6
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) ~ 0.00s 0.00s p=1.000 n=6
Total Time 17.08s (± 0.16%) 17.12s (± 0.19%) ~ 17.09s 17.17s p=0.090 n=6
vscode - node (v18.15.0, x64)
Memory used 2,923,686k (± 0.00%) 2,923,626k (± 0.00%) ~ 2,923,500k 2,923,730k p=0.378 n=6
Parse Time 11.39s (± 1.30%) 11.35s (± 0.25%) ~ 11.31s 11.39s p=0.746 n=6
Bind Time 3.54s (± 2.60%) 3.51s (± 2.50%) ~ 3.42s 3.59s p=0.516 n=6
Check Time 62.84s (± 0.44%) 62.60s (± 0.60%) ~ 62.22s 63.32s p=0.173 n=6
Emit Time 17.10s (± 7.97%) 17.66s (± 9.96%) ~ 16.49s 19.96s p=0.872 n=6
Total Time 94.87s (± 1.24%) 95.11s (± 1.60%) ~ 93.91s 97.19s p=0.810 n=6
webpack - node (v18.15.0, x64)
Memory used 409,862k (± 0.02%) 409,822k (± 0.01%) ~ 409,725k 409,901k p=0.630 n=6
Parse Time 3.95s (± 0.71%) 3.94s (± 1.10%) ~ 3.88s 3.99s p=0.418 n=6
Bind Time 1.65s (± 0.89%) 1.65s (± 1.14%) ~ 1.61s 1.66s p=1.000 n=6
Check Time 17.02s (± 0.40%) 17.02s (± 0.45%) ~ 16.94s 17.13s p=0.936 n=6
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) ~ 0.00s 0.00s p=1.000 n=6
Total Time 22.62s (± 0.39%) 22.60s (± 0.38%) ~ 22.51s 22.73s p=0.628 n=6
xstate-main - node (v18.15.0, x64)
Memory used 459,429k (± 0.02%) 459,351k (± 0.01%) ~ 459,290k 459,404k p=0.173 n=6
Parse Time 2.69s (± 0.77%) 2.69s (± 0.80%) ~ 2.67s 2.72s p=0.685 n=6
Bind Time 0.99s (± 0.64%) 0.99s (± 0.00%) ~ 0.99s 0.99s p=1.000 n=6
Check Time 15.41s (± 0.29%) 15.40s (± 0.37%) ~ 15.32s 15.48s p=0.748 n=6
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) ~ 0.00s 0.00s p=1.000 n=6
Total Time 19.09s (± 0.26%) 19.08s (± 0.35%) ~ 18.99s 19.19s p=0.520 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • Compiler-Unions - node (v18.15.0, x64)
  • angular-1 - node (v18.15.0, x64)
  • mui-docs - node (v18.15.0, x64)
  • self-build-src - node (v18.15.0, x64)
  • self-build-src-public-api - node (v18.15.0, x64)
  • self-compiler - node (v18.15.0, x64)
  • ts-pre-modules - node (v18.15.0, x64)
  • vscode - node (v18.15.0, x64)
  • webpack - node (v18.15.0, x64)
  • xstate-main - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6
Developer Information:

Download Benchmarks

typescript-bot avatar Apr 23 '24 19:04 typescript-bot

@typescript-bot pack this

weswigham avatar Apr 23 '24 20:04 weswigham

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
pack this ✅ Started ✅ Results

typescript-bot avatar Apr 23 '24 20:04 typescript-bot

Hey @weswigham, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/161430/artifacts?artifactName=tgz&fileId=11F679CBDC619C3D19F886C85532AF465B340967E85B64682C46FBF2A9BFE36A02&fileName=/typescript-5.5.0-insiders.20240423.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/[email protected]".;

typescript-bot avatar Apr 23 '24 20:04 typescript-bot

@ahejlsberg Here are the results of running the top 400 repos comparing main and refs/pull/58296/merge:

Everything looks good!

typescript-bot avatar Apr 23 '24 21:04 typescript-bot

Can you update tests/cases/compiler/APILibCheck.ts and enable this flag? This would let us ensure our public API will work with this flag; we already do this for exactOptionalPropertyTypes.

jakebailey avatar Apr 23 '24 21:04 jakebailey

nit: In the PR description the second code example does not match the preceding wording - it doesn't use readonly

robpalme avatar Apr 23 '24 22:04 robpalme

Any reason why this isn't called strictReadonly?

fatcerberus avatar Apr 23 '24 22:04 fatcerberus

Any reason why this isn't called strictReadonly?

Same reason as --exactOptionalPropertyTypes. It's too breaky to be in the --strict family of options and not necessarily desired by everyone.

ahejlsberg avatar Apr 23 '24 23:04 ahejlsberg

nit: In the PR description the second code example does not match the preceding wording - it doesn't use readonly

Only having a get accessor implies that the property is readonly, but I suppose that is a bit subtle.

ahejlsberg avatar Apr 23 '24 23:04 ahejlsberg

It's too breaky to be in the --strict family of options and not necessarily desired by everyone.

Alright, that makes sense. That being said (and I realize this is just :bike: :house: at this point) I'm still not a big fan of enforceReadonly, as the implied opposite of --enforceReadonly is "don't enforce readonly", which... isn't really accurate.

fatcerberus avatar Apr 24 '24 01:04 fatcerberus

Thanks for the PR! It looks like you've changed the TSServer protocol in some way. Please ensure that any changes here don't break consumers of the current TSServer API. For some extra review, we'll ping @sheetalkamat, @mjbvz, @zkat, and @joj for you. Feel free to loop in other consumers/maintainers if necessary.

typescript-bot avatar Apr 24 '24 21:04 typescript-bot

Tried this on a personal project (a few thousand LOC) I wrote in the 4.3 timeframe and only got one (1!!) error. Looking ahead with skipLibCheck off, there's some stuff to address in @types/node. I'll do a DT PR once the beta is out

node_modules/@types/node/globals.d.ts:85:13 - error TS2403: Subsequent variable declarations must have the same type.  Variable 'AbortSignal' must be of type '{ new (): AbortSignal; prototype: AbortSignal; abort(reason?: any): AbortSignal; any(signals: AbortSignal[]): AbortSignal; timeout(milliseconds: number): AbortSignal; }', but here has type '{ new (): AbortSignal; prototype: AbortSignal; }'.

85 declare var AbortSignal: {
               ~~~~~~~~~~~

  ../typescript/built/local/lib.dom.d.ts:2360:13
    2360 declare var AbortSignal: {
                     ~~~~~~~~~~~
    'AbortSignal' was also declared here.

node_modules/@types/node/http2.d.ts:61:22 - error TS2430: Interface 'Http2Stream' incorrectly extends interface 'Duplex'.
  Property 'destroyed' is 'readonly' in the source but not in the target.

61     export interface Http2Stream extends stream.Duplex {
                        ~~~~~~~~~~~

node_modules/@types/node/net.d.ts:75:11 - error TS2415: Class 'Socket' incorrectly extends base class 'Duplex'.
  Property 'destroyed' is 'readonly' in the source but not in the target.

75     class Socket extends stream.Duplex {
             ~~~~~~

node_modules/@types/node/stream.d.ts:480:15 - error TS2420: Class 'Writable' incorrectly implements interface 'WritableStream'.
  Property 'writable' is 'readonly' in the source but not in the target.

480         class Writable extends Stream implements NodeJS.WritableStream {
                  ~~~~~~~~

RyanCavanaugh avatar Apr 25 '24 19:04 RyanCavanaugh

Latest commits exclude methods from strict checking. A bit of rationale is in order. Consider scenarios like this:

interface Base {
    foo(): void;
}

interface Item extends Base {
    name: string;
}

interface ReadonlyItem extends Base {
    readonly name: string;
}

declare let item1: ReadonlyItem;
declare let item2: Readonly<Item>;

item1 = item2;  // Error?

It seems unreasonable to error on the assignment, yet the foo property in Readonly<Item> is a readonly function type property, whereas the foo property in ReadonlyItem is a method that isn't and can't be marked readonly. Previously this wasn't an issue because we never enforced readonly semantics across type relationships.

Methods have always been considered mutable, and we don't even allow readonly modifiers on method declarations. TBH, if we did, I think most users would read it as "this method doesn't mutate the object", which wouldn't at all be the case. Interestingly, we've had very few requests for readonly methods. It doesn't really seem to be a pain point, and mutation is commonly used in interception patterns, function prototype construction, etc.

We could consider having mapped types with readonly modifiers not add readonly to properties that originate in methods, but that just doesn't seem right either. I mean, Readonly<T> is supposed to make every property readonly.

In my opinion, the best course of action is to simply exclude methods from strict readonly checking. Meaning that, in a type relationship, when a target property is declared as a method, we don't care about the readonly state of the source property.

ahejlsberg avatar Apr 25 '24 22:04 ahejlsberg

we've had very few requests for readonly methods. It doesn't really seem to be a pain point, and mutation is commonly used in interception patterns, function prototype construction, etc.

There are some interest expressed by #22315 We could add another flag that extends the check of enforceReadonly to methods and make methods readonly by default.

Conaclos avatar May 09 '24 20:05 Conaclos

@ahejlsberg

It seems unreasonable to error on the assignment, [..]

Methods have always been considered mutable, and we don't even allow readonly modifiers on method declarations. TBH, if we did, I think most users would read it as "this method doesn't mutate the object", which wouldn't at all be the case.

I disagree here. Given enforceReadonly, I'd expect item1 = item2; // Error? to error. Also, I don't see why methods shouldn't be allowed to be declared readonly – I can (and do) already do this, and it works as I expect:

export type Type = {
	readonly toString: () => string
}

I'd like to be able to do this, purely based on consistency (I don't really need two ways to describe the same thing, so ¯\­_(ツ)_/¯):

export type Type = {
	readonly toString () : string
}

Wrapping Readonly<> around types with methods also gives us that behavior. So, from my point of view, disallowing readonly on readonly toString () : string is just an unnecessary hurdle because it merely hinders a feature that is available via different avenues anyway:

class X {
	readonly toString : () => string = () => ``
}

class Y {
	readonly toString () { // no reason why exactly this should error…
		return ``
	}
}

class Z {
	get toString () {
		return () => ``
	}
}

const x = new X()
const y = new Y()
const z = new Z()

x.toString = ()=>``
y.toString = ()=>`` // …or why exactly this should not
z.toString = ()=>``

Also, while I consider inline assignment dangerous enough, function parameters are even more dangerous because you usually do not see the code that works on your supposedly readonly data:

const readonlyArray = [] as const

mutateArray(readonlyArray) // The type 'readonly []' is 'readonly' and cannot be assigned to the mutable type 'unknown[]'.ts(2345)

function mutateArray (array : Array<unknown>) {
	array.shift()
}

const readonlyObject = { toString () { return `` } } as const

mutateObject(readonlyObject) // This should error

function mutateObject (mutable : { toString() : string }) {
	mutable.toString = () => `-`
}

mutateObject(readonlyObject) should error because otherwise, you have no guarantees that the function doesn't do something to your methods. This introduces an opportunity for runtime errors that seems preventable by making the behavior consistent.

And I don't count “interception patterns, function prototype construction, etc.” as strong counter-arguments, because:

  1. Proxy
  2. The prototype can be built as a mutable object and then assigned as a readonly prototype
  3. For runtime-safe tasks, there are usually options that work with the type system

If any specific project absolutely cannot work with this, then I guess that enforceReadonly is generally unfit anyway.

@Conaclos

We could add another flag that extends the check of enforceReadonly to methods and make methods readonly by default.

I'm unsure about the second part – I'd like to make everything readonly by default 😅 and I fear that's out of scope here… But a flag to treat methods like everything else would be good. After all, JS doesn't treat them differently, does it?

c-vetter avatar May 10 '24 09:05 c-vetter

While I welcome this direction, please do not use the term "enforce" for things that are not actually enforced. TypeScript is unsound and erased, so it cannot and does not actually enforce anything. It is useful for catching some many accidents, which is great. But some accidents will slip through the unsoundness, and much malice will. The term "enforce" implies otherwise and will give some people a false sense of security, which is dangerous.

erights avatar Jul 17 '24 20:07 erights

@erights did you have a synonym in mind?

But some accidents will slip through the unsoundness, and much malice will.

No one should be using a language with any as a defense in an adversarial situation. What scenarios are you thinking of here?

RyanCavanaugh avatar Jul 17 '24 20:07 RyanCavanaugh

Object.freeze, which is the fundamental enforcement mechanism for read-only-like stability, was introduced in ES5 specifically to be genuine enforcement which could be used in adversarial situations. Since then, Object.freeze has been used as an adversarial enforcement mechanism many times.

We are of course using Hardened JavaScript and harden in many adversarial situations. We also use TypeScript in various ways in that context. Better TypeScript readonly support is welcome for many things, including better typing of harden. However, we already find we need to continually remind users of TypeScript-on-Hardened-JS that the TypeScript typing portions of their code are unsound and erased, and therefore unenforced. When they want to enforce something, they need to rely on unerased runtime enforcement mechanisms. That's fine.

But seeing the term "enforce" in the documentation of the unenforced TypeScript properties will only deepen the confusion and make the explanations harder. It is harmful and confusing.

erights avatar Jul 17 '24 20:07 erights

a synonym in mind?

checkReadonly implies a check for type system violations without implying that they're enforced.

Another option is strictReadonly since some aspects of readonly are already checked and this is a more strict form.

turadg avatar Jul 17 '24 20:07 turadg

Regarding the name of the flag, we're perfectly happy to entertain suggestions for better options.

The issue with --checkReadonly is it would imply that no readonly checks occur when it is disabled, which isn't the case. We always disallow assignments to readonly properties.

The issue with --strictReadonly is that it implies being in the --strict family of flags. That's not the case because it would be too significant of a breaking change.

ahejlsberg avatar Jul 17 '24 23:07 ahejlsberg

Welcome to Codecov :tada:

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered :open_umbrella:

codecov-commenter avatar Jul 17 '24 23:07 codecov-commenter

Oh come on, they weren't supposed to comment that (must be the unlucky first PR, sorry)

jakebailey avatar Jul 17 '24 23:07 jakebailey

Regarding the name of the flag, we're perfectly happy to entertain suggestions for better options.

Glad to hear it! Before trying to generate more suggestions, I'd like to better understand the feature.

What surprises me is that it is a compiler flag at all, rather than a stronger form of readonly within the language alongside the existing one. As a flag, it changes meaning of existing code. Let's call the existing semantics the "weaker" constraints, and with the flag, "stronger" constraints. (I am not suggesting these names. Just need terminology to ask questions.)

What happens if I have some modules or packages that are correctly written to the weaker constraints, and other modules or packages written correctly to the stronger constraints, and now I want to correctly compose them together into a larger system. Can I statically type check this composite system? Clearly, if I check the composite only with the stronger constraints, the modules correct only by the weaker constraints will fail. If I check the composite only with the weaker constraints, if all the inputs were correct on their own terms, the composite would still pass. But the code written to the stronger constraints could come to violate them without causing a static error.

Is all this correct?

erights avatar Jul 18 '24 01:07 erights

@typescript-bot pack this

Edit: I guess that doesn't work for non members. Any chance someone could regenerate a build I could use in the playground ?

mhofman avatar Jul 18 '24 02:07 mhofman