Travis Sowoolu
Travis Sowoolu

Reputation: 63

Potential Typescript compiler bug?

I'm trying to figure out if this is typescript compiler bug or a feature

export interface Player {
    Name: string;
    Bio?: string;
    Type: 'casual' | 'pro' | 'titan' | 'nolife', 'YouDidNotDeclare';
    Online: boolean;
}

So here is the thing, it compiles just fine. Until you try to use the interface as such

 let player : Player = {Name : 'Oldum', Bio : 'Just Chilling', Type: 'casual', Online : false }

You expect the above to work just fine but then you get the error; Property ''YouDidNotDeclare'' is missing in type

Adding the said property YouDidNotDeclare clears the said error but here is the thing i realized,

On the interface declaration, I missed a '|' for a ',' on the 'Type' property.

  export interface Player {
        Name: string;
        Bio?: string;
        Type: 'casual' | 'pro' | 'titan' | 'nolife' | 'YouDidNotDeclare';
        Online: boolean;
    }

Now my question is, is it a bug or feature and if intended, in what situation will I need to use such a feature?

Upvotes: 2

Views: 144

Answers (1)

jcalz
jcalz

Reputation: 327744

It's not a compiler bug; it's a set of features that you are not intending to use.


First, in interface and other purely type-level object type definitions, the compiler interprets a comma (,) as a separator between members, just like a semicolon (;):

// the following interfaces are equivalent
interface Semicolons { foo: string; bar: number; }
interface Commas { foo: string, bar: number }

That means 'nolife' is considered the end of the Type member declaration.


Second, you are allowed to put property names inside quotation marks:

// the following two interfaces are equivalent
interface Bare { foo: string }
interface Quoted { "foo": string }

This is helpful when an object type has a property that isn't a valid JavaScript identifier, like "uses-dashes" or even just to be consistent with JSON specifications.

That means YouDidNotDeclare is considered to be the name of a property.


Finally, TypeScript does not require type annotations. For variable or function parameters declared without explicit type annotations, sometimes there is enough contextual information for the compiler to infer a reasonable type for them:

const foo = { a: 123 };
// foo inferred as { a: number; }

[1, 2, 3].map(x => x * 3);
// x inferred as number

Such implicit types are often quite desirable and it's considered good practice for you to leave off type annotations in situations like this where the inferred types are what you intended.

But there are other situations in which there is not enough contextual information for reasonable type inference. When this happens, the compiler gives up and infers the any "type":

// the following two interfaces are equivalent
interface Explicit { foo: any }
interface Implicit { foo }

Thus your interface is equivalent to the following:

export interface Hater {
    Name: string;
    Bio?: string;
    Type: 'casual' | 'pro' | 'titan' | 'nolife';
    YouDidNotDeclare: any;
    Online: boolean;
}

which is definitely not what you intended.


In fact, it is so rarely useful for the compiler to implicitly infer an any type like it does with YouDidNotDeclare, that there's a recommended compiler option --noImplicitAny which produces a compiler warning whenever this happens. This compiler option can be enabled by itself, or along with a whole suite of stronger type checking options via the --strict compiler option.

If you enable those, your interface definition will immediately warn you that something weird has happened:

Type: 'casual' | 'pro' | 'titan' | 'nolife', 'YouDidNotDeclare'; // error!
// ----------------------------------------> ~~~~~~~~~~~~~~~~~~
// Member ''YouDidNotDeclare'' implicitly has an 'any' type.

Which should allow you to fix the definition to the version you intended it to be.

Playground link to code

Upvotes: 3

Related Questions