Issue 1:
This proposal serves, at this point, as an trial-and-error experience for myself and to give you guys some ideas to play with.. I understand this may not be 'it' in current form (or not at all) so don't just stamp it with that dreaded "DECLINED" thing. I'll just close it myself after you explain to me how bad it is and why.. and all the edge cases..(e.g. expressions in extends clauses etc.) or who knows, I may be even able to fix it later..

Let A and B be two classes and a: A, b: B.

For the structurally valid assignment a = b.

Either one of the following conditions should hold to error:

B is a strict nominal ancestor of A.

A is a nominal ancestor of B but with an incompatible generic signature.

Examples:

class Base {
action = "START WORLD WAR III";
}

class Derived extends Base {
action = "SAVE THE WORLD";
}

function giveMeDerived(shouldBeDerived: Derived) {
performAction(shouldBeDerived.action);
}

giveMeDerived(new Base()); // Error! The compiler has saved the world!

class Bag<T> {
items = [];
}

function putStringsInTheBag(bag: Bag<string>) {
bag.items.push("a", "b", "c", "d");
}

let bagOfNumbers = new Bag<number>();
putStringsInTheBag(bagOfNumbers); // Error! no strings going in this bag..

class Bag<T> {
items = [];
}

class BagWithMethod<V> extends Bag<V> {
putItem(item: V) {
this.items.push(item);
}
}

let bagWithMethod = new BagWithMethod<number>();
bagWithMethod.putItem(42);

let evilBag: Bag<string> = bagWithMethod; // Error! evil bag detected!

Note: this could be opt-in through a compiler flag, e.g. --nominalClassChecks..

Issue 2:
Proposal: support non-structural typing (e.g. new user-defined base-types, or some form of basic nominal typing). This allows programmer to have more refined types supporting frequently used idioms such as:

Indexes that come from different tables. Because all indexes are strings (or numbers), it's easy to use the an index variable (intended for one table) with another index variable intended for a different table. Because indexes are the same type, no error is given. If we have abstract index classes this would be fixed.

Certain classes of functions (e.g. callbacks) can be important to be distinguished even though they have the same type. e.g. "() => void" often captures a side-effect producing function. Sometimes you want to control which ones are put into an event handler. Currently there's no way to type-check them.

Consider having 2 different interfaces that have different optional parameters but the same required one. In typescript you will not get a compiler error when you provide one but need the other. Sometimes this is ok, but very often this is very not ok and you would love to have a compiler error rather than be confused at run-time.

Proposal (with all type-Error-lines removed!):

// Define FooTable and FooIndex
nominal FooIndex = string; // Proposed new kind of nominal declaration.
interface FooTable {
[i: FooIndex]: { foo: number };
}
let s1: FooIndex;
let t1: FooTable;

// Define BarTable and BarIndex
nominal BarIndex = string; // Proposed new kind of nominal declaration.
interface BarTable {
[i: BarIndex]: { bar: string };
}
let s2: BarIndex;
let t2: BarTable;

// For assignment from base-types and basic structures: no type-overloading is needed.
s1 = 'foo1';
t1 = {};
t1[s1] = { foo: 1 };

s2 = 'bar1';
t2 = { 'bar1': { bar: 'barbar' }};

console.log(s2 = s1); // Proposed to be type error.
console.log(s2 == s1); // Proposed to be type error.
console.log(s2 === s1); // Proposed to be type error.

t1[s2].foo = 100; // Gives a runtime error. Proposed to be type error.
t1[s1].foo = 100;

function BadFooTest(t: FooTable) {
if (s2 in t) { // Proposed to be type error.
console.log('cool');
console.log(t[s2].foo); // Proposed to be type error.
}
}

function GoodBarTest(t: BarTable) {
if (s2 in t) {
console.log('cool');
console.log(t[s2].bar);
}
}

BadFooTest(t1); // Gives runtime error;
BadFooTest(t2); // No runtime error, Proposed to be type error.
GoodBarTest(t1); // Gives runtime error; Proposed to be type error.
GoodBarTest(t2);