<p dir="auto">I wish I could turn duck-typing off sometimes because it leads to bugs that very hard to detect.</p>
<p dir="auto">For example:<br>
Suppose I have 2 interfaces with the same surface but completely unrelated sematics:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="interface Chicken {
   id: number;
   name: string;
}

interface JetPlane {
   id: number;
   name: string;
}"><pre class="notranslate"><code class="notranslate">interface Chicken {
   id: number;
   name: string;
}

interface JetPlane {
   id: number;
   name: string;
}
</code></pre></div>
<p dir="auto">then doing the following is completely fine in TypeScript:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="var chicken : Chicken = { id: 1, name: 'Thomas' };
var plane: JetPlane = { id: 2, name: 'F 35' };
chicken = plane;"><pre class="notranslate"><code class="notranslate">var chicken : Chicken = { id: 1, name: 'Thomas' };
var plane: JetPlane = { id: 2, name: 'F 35' };
chicken = plane;
</code></pre></div>
<p dir="auto">I wish there was an option to prevent such assignment from being valid.</p>
<p dir="auto">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:</p>
<ol dir="auto">
<li>
<p dir="auto">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.</p>
</li>
<li>
<p dir="auto">Certain classes of functions (e.g. callbacks) can be important to be distinguished even though they have the same type. e.g. "() =&gt; 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.</p>
</li>
<li>
<p dir="auto">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.</p>
</li>
</ol>
<p dir="auto">Proposal (with all type-Error-lines removed!):</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="// 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);"><pre class="notranslate"><code class="notranslate">// 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);
</code></pre></div>