Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent compatibility of generic interfaces #20738

Closed
c7hm4r opened this issue Dec 17, 2017 · 4 comments
Closed

Inconsistent compatibility of generic interfaces #20738

c7hm4r opened this issue Dec 17, 2017 · 4 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@c7hm4r
Copy link

c7hm4r commented Dec 17, 2017

TypeScript Version: 2.7.0-dev.20171216

Code

class Base {}
class Derived extends Base {
  public derived = true;
}

interface ISetter0<T> {
  set(x: T): void;
}
interface ISetter1<T> {
  set: (x: T) => void;
}

const derivedSetter0: ISetter0<Derived> = { set: () => null }; // assignment 0
const derivedSetter1: ISetter1<Derived> = { set: () => null }; // assignment 1

let baseSetter0: ISetter0<Base> = derivedSetter0; // assignment 2
let baseSetter1: ISetter1<Base> = derivedSetter1; // assignment 3

baseSetter0 = derivedSetter1; // assignment 4
baseSetter1 = derivedSetter0; // assignment 5

Compiler options:

  "compilerOptions": {
    "strict": true,
    "strictFunctionTypes": true,
  },

Expected behavior:

Compilation should fail at assignments 2, 3, 4 and 5.

Actual behavior:

Compilation only fails at assigments 3 and 5.

tsc output:

test.ts(17,5): error TS2322: Type 'ISetter1<Derived>' is not assignable to type 'ISetter1<Base>'.
  Type 'Base' is not assignable to type 'Derived'.
    Property 'derived' is missing in type 'Base'.
test.ts(20,1): error TS2322: Type 'ISetter0<Derived>' is not assignable to type 'ISetter1<Base>'.
  Types of property 'set' are incompatible.
    Type '(x: Derived) => void' is not assignable to type '(x: Base) => void'.
      Types of parameters 'x' and 'x' are incompatible.
        Type 'Base' is not assignable to type 'Derived'.

Additional Notes:

The expected behavior handles contravariance correctly, the actual behavior not.

This bug could be part of the more general issue “Covariance / Contravariance Annotations”, but a full covariance / contravariance implementation is probably not necessary to fix this bug. It would probably also be fixed if
“Proposal: covariance and contravariance generic type arguments annotations” is implemented.

@c7hm4r c7hm4r changed the title Inconsistent compatibility checking of generics Inconsistent compatibility checking of generic interfaces Dec 17, 2017
@c7hm4r c7hm4r changed the title Inconsistent compatibility checking of generic interfaces Inconsistent compatibility of generic interfaces Dec 17, 2017
@ahejlsberg
Copy link
Member

This is working as intended. The --strictFunctionTypes mode doesn't affect properties declared using method syntax. See #18654 for the rationale.

@ahejlsberg ahejlsberg added the Working as Intended The behavior described is the intended behavior; this is not a bug label Dec 17, 2017
@c7hm4r
Copy link
Author

c7hm4r commented Dec 17, 2017

To simulate properties with strict type checking currently it seems to be the simplest solution to define a getter and setter, going without a more concise syntax. For example:

interface IBox<T> {
  getValue: () => T;
  setValue: (value: T) => void;
}

Instead of:

interface IBox<T> {
  value: T;
}

To implement the above interface and achieve type safety I think it is necessary to either:

  • use methods and hide the implementing class (via ES6 modules) or
  • to refuse methods and use read-only properties, which probably has a negative runtime performance impact.

In my opinion it is too restrictive that the contravariance of a function depends on whether it is a method or a property. I would be pleased if strict type checking for methods is introduced in TypeScript in the future—I am used to C#.

@RyanCavanaugh
Copy link
Member

Unfortunately the DOM itself is constructed in a way where it can't be correctly typed without either adding write-only properties (ugh) or introducing semantics for inheritance-isn't-subtyping. All the element event methods have a callback that passes the element type itself, which means they can be incorrectly invoked through a supertype reference.

@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants