Skip to content

Types declared as an "interface" do not extend Record<PropertyKey, unknown> #42825

Closed
@Seally

Description

@Seally

Bug Report

🔎 Search Terms

  • interface type
  • inconsistent interface type extends

🕗 Version & Regression Information

  • This is the behavior in every version I tried*, and I reviewed the FAQ for entries about missing index signatures and type assignment.

*Checked 4.1.3, 4.1.5, current beta, nightly, and some previous versions.

⏯ Playground Link

Playground link with relevant code

💻 Code

type TFoo = {
    type: string;
    value: number;
};

interface IFoo {
    type: string;
    value: number;
}

// Just to check if "interface-ness" is carried over through type assignment.
type ITFoo = IFoo;

interface AnyObject {
    [key: string]: unknown;
}

type AnyObjectType = {
    [key: string]: unknown;
};

type TypeTest = TFoo extends Record<PropertyKey, unknown> ? true : false;            // => true
type InterfaceTest = IFoo extends Record<PropertyKey, unknown> ? true : false;       // => false (expected 'true')
type InterfaceTypeTest = ITFoo extends Record<PropertyKey, unknown> ? true : false;  // => false (expected 'true') 

type TypeTest2 = TFoo extends { [key: string]: unknown } ? true : false;            // => true
type InterfaceTest2 = IFoo extends { [key: string]: unknown } ? true : false;       // => false (expected 'true')
type InterfaceTypeTest2 = ITFoo extends { [key: string]: unknown } ? true : false;  // => false (expected 'true') 

type TypeTest3 = TFoo extends AnyObject ? true : false;            // => true
type InterfaceTest3 = IFoo extends AnyObject ? true : false;       // => false (expected 'true')
type InterfaceTypeTest3 = ITFoo extends AnyObject ? true : false;  // => false (expected 'true') 

type TypeTest4 = TFoo extends AnyObjectType ? true : false;            // => true
type InterfaceTest4 = IFoo extends AnyObjectType ? true : false;       // => false (expected 'true')
type InterfaceTypeTest4 = ITFoo extends AnyObjectType ? true : false;  // => false (expected 'true') 

function checkObject(obj: Record<PropertyKey, unknown>) {}

const fooType: TFoo = {
    type: "misc",
    value: 0
};

const fooInterface: IFoo = {
    type: "number",
    value: 20
};

const fooInterfaceType: ITFoo = {
    type: "number",
    value: 30
};

checkObject(fooType);
checkObject(fooInterface); // type error
checkObject(fooInterfaceType); // type error

🙁 Actual behavior

InterfaceTest and InterfaceTypeTest (all variations) are assigned the type of false, while the various TypeTests all return true.

Additionally, fooInterface and fooInterfaceType fails type check when passed to the stub function checkObject() with the error code 2345 (missing index signature).

🙂 Expected behavior

TypeTest, InterfaceTest, and InterfaceTypeTest (all variations) should be assigned the type true, and the three usages of the function checkObject() passes type check.

My understanding of unknown is that every type extends unknown (like any) and unknown extends only unknown, so object types of any shape should extend Record<PropertyKey, unknown>. From this is why I think the correct result is that all 3 variants of Foo extends Record<PropertyKey, unknown>.

Also, even if that assumption is incorrect, the types TFoo and IFoo only differ in that they're declared as a type or an interface. I expect that this means that TFoo and IFoo should be treated identically by various type constraints.

I initially encountered this issue when I was trying to pass an object with an interface-declared type to a function in the Deno standard library (specifically assertObjectMatch() under "testing/asserts" which accepts two arguments of type Record<PropertyKey, unknown>).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions