Search code examples
typescriptinterfaceshadowing

How to prevent shadowing global interface?


I stumbled upon a problem with "shadowing" global-interfaces in Typescript:

live-code-example

That is the following code refused to type-check:

interface Location {
    location: string;
}

interface Named {
    name: string;
}

interface NamedLoc extends Named, Location {
    s: string;
}

let dd: NamedLoc = {
    s: '3',
    name: "asasf",
    location: "asd"
};

with the following Error:

error TS2322: Type '{ s: string; name: string; location: string; }' is not assignable to type 'NamedLoc'.
  Property 'hash' is missing in type '{ s: string; name: string; location: string; }'.

13 let dd: NamedLoc = {
       ~~

Note, that I defined MY OWN Location interface, but SOMEHOW typescript took the definition of Location interface from:

interface Location {
    hash: string;
    host: string;
    hostname: string;
    href: string;
    readonly origin: string;
    pathname: string;
    port: string;
    protocol: string;
    search: string;
    assign(url: string): void;
    reload(forcedReload?: boolean): void;
    replace(url: string): void;
    toString(): string;
}

This, however, type-checks !!?

interface Loc {
    location: string;
}

interface Named {
    name: string;
}

interface NamedLoc extends Named, Loc {
    s: string;
}

let dd: NamedLoc = {
    s: '3',
    name: "asasf",
    location: "asd"
};

It took me some time to figure out that the type I defined is not used here, but rather something else must be going on, as even my editor jumped into the definition of localy-defined interface.

What will happen if, say in the future there will be introduced another global-type that happens to have the same name as one of the types in my current-app? Will it no longer type-check?

Why is it like this, what is going on behind the sceens?

PS I was following TS-docs:

Extending-interfaces on ts-docs


Solution

  • Here is an explanation of what you are seeing. It can be reproduced with this short example.

    interface Location {
        location: string;
    }
    
    // Error!
    const loc: Location = {
        location: ''
    }
    

    What you are experiencing is that multiple declarations within the same common root contribute to the same type.

    So if your Location interface is global, it will add to the existing Location interface that is part of lib.d.ts.

    You can avoid this simply either with modules or namespaces:

    namespace Example {
        interface Location {
            location: string;
        }
    
        // This works
        const loc: Location = {
            location: ''
        }
    }
    

    If your file either imports or exports, it will be a module and won't have the issue, just the same as in the namespace example.