microsoft / microsoft/TypeScript

[3.5.0-dev.20190516] Incorrect type error for mixin

Open
#31,426 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Bug Domain: classes Needs Human Review
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

When using the mixin pattern, there are 2 main notations to define the type of the mixin entity.
The mixin pattern:

export type AnyFunction<A = any>        = (...input : any[]) => A
export type AnyConstructor<A = object>  = new (...input : any[]) => A
export type Mixin<T extends AnyFunction> = InstanceType<ReturnType<T>>

export const Box = <T extends AnyConstructor<object>>(base : T) =>
class Box extends base {
    value       : any
}

1st notation:

export type Box = Mixin<typeof Box> {}

2nd notation:

export interface Box extends Mixin<typeof Box> {}

The 1st notation can not be used for recursive definitions (#29872). Because of that we primarily use 2nd notation. It works fine in most cases, however I found a case, when it produces invalid type error. The full snippet to reproduce the problem below.

Note:

  • The typechecker correctly figures out that there's no zxc property on this, inside the observe method of Quark mixin.
  • In that method, it does not complain about the this.value usage
  • It does complain, when value is used on function argument
  • If you'll switch the Quark mixin to the 1st notation, the error will disappear

Expected behavior:

  • No type errors for the definition of test function below
export type AnyFunction<A = any>        = (...input : any[]) => A
export type AnyConstructor<A = object>  = new (...input : any[]) => A
export type Mixin<T extends AnyFunction> = InstanceType<ReturnType<T>>

export const Box = <T extends AnyConstructor<object>>(base : T) =>
class Box extends base {
    value       : any
}
export interface Box extends Mixin<typeof Box> {}

export const Observable = <T extends AnyConstructor<object>>(base : T) =>
class Observable extends base {
    observe () : Quark {
        return
    }
}
export interface Observable extends Mixin<typeof Observable> {}

export const Quark = <T extends AnyConstructor<Box & Observable>>(base : T) =>
class Quark extends base {

    observe () : Quark {
        // No error here!
        this.value
        
        // error: Error:(28, 14) TS2339: Property 'zxc' does not exist on type 'Quark'.
        this.zxc
        
        return
    }
}
export interface Quark extends Mixin<typeof Quark> {}

const test = (a : Quark) => a.value // <-- Error:(35, 28) TS2339: Property 'value' does not exist on type 'Quark'.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by compiling the complete TypeScript reproducer in this issue and compare the two Quark mixin notations. Trace how the checker resolves the inherited value property inside Quark and on the test function argument. Done means the second notation produces no type error for a.value while the invalid this.zxc access still reports an error.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.