Skip to content

Strict assignment #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions libdef.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @strict-assignment
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this line results in code in test.ts using these declarations with strict-assignment checking even if when using the --strict-assignment flag.


declare class Animal {}
declare class Cat { purr(): void }
declare class Dog { bark(): void }

declare function foo(animals: Animal[]): void;

type CatNode = { animal: Cat };
type AnimalNode = { animal: Animal };
type ReadonlyAnimalNode = { animal: Readonly<Animal> };

type CatsNode = { animals: Cat[] };
type AnimalsNode = { animals: Animal[] };
type ReadonlyAnimalsNode = { animals: ReadonlyArray<Animal> };
191 changes: 147 additions & 44 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,14 @@ namespace ts {
category: Diagnostics.Additional_Checks,
description: Diagnostics.Report_errors_for_fallthrough_cases_in_switch_statement
},
{
name: "strictAssignment",
type: "boolean",
affectsSemanticDiagnostics: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Additional_Checks,
description: Diagnostics.Enable_strict_checking_of_assignments
},

// Module Resolution
{
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2621,6 +2621,14 @@
"category": "Error",
"code": 2754
},
"Type '{0}' is not assignable to type '{1}'. Covariant assignment of aliases to non-readonly targets are unsafe.": {
"category": "Error",
"code": 2755
},
"Argument of type '{0}' is not assignable to parameter of type '{1}'. Covariant assignment of aliases to non-readonly targets are unsafe.": {
"category": "Error",
"code": 2756
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down Expand Up @@ -4109,6 +4117,10 @@
"category": "Message",
"code": 6502
},
"Enable strict checking of assignments.": {
"category": "Message",
"code": 6503
},

"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
Expand Down
1 change: 1 addition & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7883,6 +7883,7 @@ namespace ts {
break;
}
case "jsx": return; // Accessed directly
case "strict-assignment": return; // Accessed directly
default: Debug.fail("Unhandled pragma kind"); // Can this be made into an assertNever in the future?
}
});
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4701,6 +4701,7 @@ namespace ts {
strictBindCallApply?: boolean; // Always combine with strict property
strictNullChecks?: boolean; // Always combine with strict property
strictPropertyInitialization?: boolean; // Always combine with strict property
strictAssignment?: boolean; // Always combine with strict property
stripInternal?: boolean;
suppressExcessPropertyErrors?: boolean;
suppressImplicitAnyIndexErrors?: boolean;
Expand Down Expand Up @@ -6030,6 +6031,9 @@ namespace ts {
args: [{ name: "factory" }],
kind: PragmaKindFlags.MultiLine
},
"strict-assignment": {
kind: PragmaKindFlags.SingleLine
},
} as const;

/* @internal */
Expand Down
29 changes: 29 additions & 0 deletions test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/// <reference path="./libdef.d.ts" />

module TestAssignment {
type Node = { id: number | string };
type NumNode = { id: number};

const numNode: NumNode = { id: 5 };
const node1: Node = numNode; // error, prevent setting id to a string
node1.id = "five";

const node2: Node = { id: 5 }; // okay, since the source is an object literal
node2.id = "five";

const readonlyNode: Readonly<Node> = numNode;
readonlyNode.id = "five"; // error, readonlyNode is readonly

const cats: Cat[] = [new Cat];

foo(cats); // error, prevent a Dog from getting added to cats
foo([new Cat]); // okay

const catNode: CatNode = { animal: new Cat };
const cat = new Cat;

const animalNode1: AnimalNode = catNode; // error
const animalNode2: AnimalNode = { animal: new Cat }; // okay
const animalNode3: AnimalNode = { animal: cat }; // okay
const animalNode4: Readonly<AnimalNode> = catNode; // okay
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"strictAssignment": true
}
}
110 changes: 110 additions & 0 deletions tests/baselines/reference/strictAssignment1.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
tests/cases/compiler/strictAssignment1.ts(10,11): error TS2755: Type 'Cat[]' is not assignable to type 'Animal[]'. Covariant assignment of aliases to non-readonly targets are unsafe.
tests/cases/compiler/strictAssignment1.ts(24,11): error TS2755: Type 'CatNode' is not assignable to type 'AnimalNode'. Covariant assignment of aliases to non-readonly targets are unsafe.
tests/cases/compiler/strictAssignment1.ts(40,11): error TS2755: Type 'CatsNode' is not assignable to type 'AnimalsNode'. Covariant assignment of aliases to non-readonly targets are unsafe.
tests/cases/compiler/strictAssignment1.ts(47,11): error TS2322: Type '{ animals: Cat[]; }' is not assignable to type 'AnimalsNode'.
Types of property 'animals' are incompatible.
Type 'Cat[]' is not assignable to type 'Animal[]'. Covariant assignment of aliases to non-readonly targets are unsafe.
tests/cases/compiler/strictAssignment1.ts(55,11): error TS2755: Type 'CatsNode' is not assignable to type 'ReadonlyAnimalsNode'. Covariant assignment of aliases to non-readonly targets are unsafe.
tests/cases/compiler/strictAssignment1.ts(64,11): error TS2322: Type 'CatsNode' is not assignable to type 'Readonly<AnimalsNode>'.
Types of property 'animals' are incompatible.
Type 'Cat[]' is not assignable to type 'Animal[]'. Covariant assignment of aliases to non-readonly targets are unsafe.
tests/cases/compiler/strictAssignment1.ts(69,11): error TS2322: Type '{ animals: Cat[]; }' is not assignable to type 'Readonly<AnimalsNode>'.
Types of property 'animals' are incompatible.
Type 'Cat[]' is not assignable to type 'Animal[]'. Covariant assignment of aliases to non-readonly targets are unsafe.


==== tests/cases/compiler/strictAssignment1.ts (7 errors) ====
module StrictAssignment1 {
class Animal {}
class Cat { purr() {} }
class Dog { bark() {} }

// Arrays
const cats: Cat[] = [new Cat];

// TODO: write tests that show the order of the next two statements don't matter
const animals1: Animal[] = cats; // error: alias assignments are invariant
~~~~~~~~
!!! error TS2755: Type 'Cat[]' is not assignable to type 'Animal[]'. Covariant assignment of aliases to non-readonly targets are unsafe.
!!! related TS2728 tests/cases/compiler/strictAssignment1.ts:3:17: 'purr' is declared here.
const animals2: Animal[] = [new Cat]; // okay: covariant is safe since source is a literal
const animals3: ReadonlyArray<Animal> = cats; // okay: covariant is safe since target is readonly

// TODO: check assignments as well as variable declarations

// // Simple Objects
type CatNode = { animal: Cat };
type AnimalNode = { animal: Animal };
type ReadonlyAnimalNode = { animal: Readonly<Animal> };

const catNode: CatNode = { animal: new Cat };
const cat = new Cat;

const animalNode1: AnimalNode = catNode; // error
~~~~~~~~~~~
!!! error TS2755: Type 'CatNode' is not assignable to type 'AnimalNode'. Covariant assignment of aliases to non-readonly targets are unsafe.
const animalNode2: AnimalNode = { animal: new Cat }; // okay
const animalNode3: AnimalNode = { animal: cat }; // okay
const animalNode4: Readonly<AnimalNode> = catNode; // okay

// Need to check if the target is readonly
const animalNode5: ReadonlyAnimalNode = { animal: cat }; // okay

// Nested objects
type CatsNode = { animals: Cat[] };
type AnimalsNode = { animals: Animal[] };
type ReadonlyAnimalsNode = { animals: ReadonlyArray<Animal> };

const catsNode: CatsNode = { animals: [new Cat] };
// const cats: Cat[] = [new Cat];

const animalsNode1: AnimalsNode = catsNode; // error
~~~~~~~~~~~~
!!! error TS2755: Type 'CatsNode' is not assignable to type 'AnimalsNode'. Covariant assignment of aliases to non-readonly targets are unsafe.
// prevents animalsNode.animals = [new Dog] which would conflict
// with CatsNode's definition of its animals property

const animalsNode2: AnimalsNode = { animals: [new Cat] }; // okay
// okay since we're not storing [new Cat] in a variable typed as a Cat[]

const animalsNode3: AnimalsNode = { animals: cats }; // error
~~~~~~~~~~~~
!!! error TS2322: Type '{ animals: Cat[]; }' is not assignable to type 'AnimalsNode'.
!!! error TS2322: Types of property 'animals' are incompatible.
!!! error TS2322: Type 'Cat[]' is not assignable to type 'Animal[]'. Covariant assignment of aliases to non-readonly targets are unsafe.
// prevents animalsNode.animals.push(new Dog) which would add
// a Dog to cats array

const animalsNode4: ReadonlyAnimalsNode = { animals: cats }; // okay
// okay since the animals property is a readonly array and we are unable
// to add new elements to it.

const animalsNode5: ReadonlyAnimalsNode = catsNode; // error
~~~~~~~~~~~~
!!! error TS2755: Type 'CatsNode' is not assignable to type 'ReadonlyAnimalsNode'. Covariant assignment of aliases to non-readonly targets are unsafe.
// prevents animalsNode.animals = [new Dog] which would conflict
// with CatsNode's definition of its animals property

const animalsNode6: Readonly<ReadonlyAnimalsNode> = catsNode; // okay
// prevents both setting animals property to different from Cats[] and
// prevents adding a Dog to the animals array which is typed as a Cats[]
// on the right side

const animalsNode7: Readonly<AnimalsNode> = catsNode; // error
~~~~~~~~~~~~
!!! error TS2322: Type 'CatsNode' is not assignable to type 'Readonly<AnimalsNode>'.
!!! error TS2322: Types of property 'animals' are incompatible.
!!! error TS2322: Type 'Cat[]' is not assignable to type 'Animal[]'. Covariant assignment of aliases to non-readonly targets are unsafe.
// while making AnimalsNode readonly prevents the reassignment of animals
// to something other than Cat[], it's still possible to push a Dog to catsNode's
// animals array.

const animalsNode8: Readonly<AnimalsNode> = { animals: cats }; // error
~~~~~~~~~~~~
!!! error TS2322: Type '{ animals: Cat[]; }' is not assignable to type 'Readonly<AnimalsNode>'.
!!! error TS2322: Types of property 'animals' are incompatible.
!!! error TS2322: Type 'Cat[]' is not assignable to type 'Animal[]'. Covariant assignment of aliases to non-readonly targets are unsafe.

const animalsNode9: Readonly<ReadonlyAnimalsNode> = { animals: cats };
}

136 changes: 136 additions & 0 deletions tests/baselines/reference/strictAssignment1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//// [strictAssignment1.ts]
module StrictAssignment1 {
class Animal {}
class Cat { purr() {} }
class Dog { bark() {} }

// Arrays
const cats: Cat[] = [new Cat];

// TODO: write tests that show the order of the next two statements don't matter
const animals1: Animal[] = cats; // error: alias assignments are invariant
const animals2: Animal[] = [new Cat]; // okay: covariant is safe since source is a literal
const animals3: ReadonlyArray<Animal> = cats; // okay: covariant is safe since target is readonly

// TODO: check assignments as well as variable declarations

// // Simple Objects
type CatNode = { animal: Cat };
type AnimalNode = { animal: Animal };
type ReadonlyAnimalNode = { animal: Readonly<Animal> };

const catNode: CatNode = { animal: new Cat };
const cat = new Cat;

const animalNode1: AnimalNode = catNode; // error
const animalNode2: AnimalNode = { animal: new Cat }; // okay
const animalNode3: AnimalNode = { animal: cat }; // okay
const animalNode4: Readonly<AnimalNode> = catNode; // okay

// Need to check if the target is readonly
const animalNode5: ReadonlyAnimalNode = { animal: cat }; // okay

// Nested objects
type CatsNode = { animals: Cat[] };
type AnimalsNode = { animals: Animal[] };
type ReadonlyAnimalsNode = { animals: ReadonlyArray<Animal> };

const catsNode: CatsNode = { animals: [new Cat] };
// const cats: Cat[] = [new Cat];

const animalsNode1: AnimalsNode = catsNode; // error
// prevents animalsNode.animals = [new Dog] which would conflict
// with CatsNode's definition of its animals property

const animalsNode2: AnimalsNode = { animals: [new Cat] }; // okay
// okay since we're not storing [new Cat] in a variable typed as a Cat[]

const animalsNode3: AnimalsNode = { animals: cats }; // error
// prevents animalsNode.animals.push(new Dog) which would add
// a Dog to cats array

const animalsNode4: ReadonlyAnimalsNode = { animals: cats }; // okay
// okay since the animals property is a readonly array and we are unable
// to add new elements to it.

const animalsNode5: ReadonlyAnimalsNode = catsNode; // error
// prevents animalsNode.animals = [new Dog] which would conflict
// with CatsNode's definition of its animals property

const animalsNode6: Readonly<ReadonlyAnimalsNode> = catsNode; // okay
// prevents both setting animals property to different from Cats[] and
// prevents adding a Dog to the animals array which is typed as a Cats[]
// on the right side

const animalsNode7: Readonly<AnimalsNode> = catsNode; // error
// while making AnimalsNode readonly prevents the reassignment of animals
// to something other than Cat[], it's still possible to push a Dog to catsNode's
// animals array.

const animalsNode8: Readonly<AnimalsNode> = { animals: cats }; // error

const animalsNode9: Readonly<ReadonlyAnimalsNode> = { animals: cats };
}


//// [strictAssignment1.js]
var StrictAssignment1;
(function (StrictAssignment1) {
var Animal = /** @class */ (function () {
function Animal() {
}
return Animal;
}());
var Cat = /** @class */ (function () {
function Cat() {
}
Cat.prototype.purr = function () { };
return Cat;
}());
var Dog = /** @class */ (function () {
function Dog() {
}
Dog.prototype.bark = function () { };
return Dog;
}());
// Arrays
var cats = [new Cat];
// TODO: write tests that show the order of the next two statements don't matter
var animals1 = cats; // error: alias assignments are invariant
var animals2 = [new Cat]; // okay: covariant is safe since source is a literal
var animals3 = cats; // okay: covariant is safe since target is readonly
var catNode = { animal: new Cat };
var cat = new Cat;
var animalNode1 = catNode; // error
var animalNode2 = { animal: new Cat }; // okay
var animalNode3 = { animal: cat }; // okay
var animalNode4 = catNode; // okay
// Need to check if the target is readonly
var animalNode5 = { animal: cat }; // okay
var catsNode = { animals: [new Cat] };
// const cats: Cat[] = [new Cat];
var animalsNode1 = catsNode; // error
// prevents animalsNode.animals = [new Dog] which would conflict
// with CatsNode's definition of its animals property
var animalsNode2 = { animals: [new Cat] }; // okay
// okay since we're not storing [new Cat] in a variable typed as a Cat[]
var animalsNode3 = { animals: cats }; // error
// prevents animalsNode.animals.push(new Dog) which would add
// a Dog to cats array
var animalsNode4 = { animals: cats }; // okay
// okay since the animals property is a readonly array and we are unable
// to add new elements to it.
var animalsNode5 = catsNode; // error
// prevents animalsNode.animals = [new Dog] which would conflict
// with CatsNode's definition of its animals property
var animalsNode6 = catsNode; // okay
// prevents both setting animals property to different from Cats[] and
// prevents adding a Dog to the animals array which is typed as a Cats[]
// on the right side
var animalsNode7 = catsNode; // error
// while making AnimalsNode readonly prevents the reassignment of animals
// to something other than Cat[], it's still possible to push a Dog to catsNode's
// animals array.
var animalsNode8 = { animals: cats }; // error
var animalsNode9 = { animals: cats };
})(StrictAssignment1 || (StrictAssignment1 = {}));
Loading