Skip to content

Fix 'keyof' for constrained type parameters #12026

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

Merged
merged 5 commits into from
Nov 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6825,6 +6825,27 @@ namespace ts {
}
}

if (target.flags & TypeFlags.TypeParameter) {
// Given a type parameter K with a constraint keyof T, a type S is
// assignable to K if S is assignable to keyof T.
const constraint = getConstraintOfTypeParameter(<TypeParameter>target);
if (constraint && constraint.flags & TypeFlags.Index) {
if (result = isRelatedTo(source, constraint, reportErrors)) {
return result;
}
}
}
else if (target.flags & TypeFlags.Index) {
// Given a type parameter T with a constraint C, a type S is assignable to
// keyof T if S is assignable to keyof C.
const constraint = getConstraintOfTypeParameter((<IndexType>target).type);
if (constraint) {
if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) {
return result;
}
}
}

if (source.flags & TypeFlags.TypeParameter) {
let constraint = getConstraintOfTypeParameter(<TypeParameter>source);

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2709,7 +2709,7 @@ namespace ts {
EnumLike = Enum | EnumLiteral,
UnionOrIntersection = Union | Intersection,
StructuredType = Object | Union | Intersection,
StructuredOrTypeParameter = StructuredType | TypeParameter,
StructuredOrTypeParameter = StructuredType | TypeParameter | Index,

// 'Narrowable' types are types where narrowing actually narrows.
// This *should* be every type other than null, undefined, void, and never
Expand Down
120 changes: 120 additions & 0 deletions tests/baselines/reference/keyofAndIndexedAccess.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ class Shape {
visible: boolean;
}

class TaggedShape extends Shape {
tag: string;
}

class Item {
name: string;
price: number;
Expand Down Expand Up @@ -149,6 +153,17 @@ function f32<K extends "width" | "height">(key: K) {
return shape[key]; // Shape[K]
}

function f33<S extends Shape, K extends keyof S>(shape: S, key: K) {
let name = getProperty(shape, "name");
let prop = getProperty(shape, key);
return prop;
}

function f34(ts: TaggedShape) {
let tag1 = f33(ts, "tag");
let tag2 = getProperty(ts, "tag");
}

class C {
public x: string;
protected y: string;
Expand All @@ -164,14 +179,58 @@ function f40(c: C) {
let x: X = c["x"];
let y: Y = c["y"];
let z: Z = c["z"];
}

// Repros from #12011

class Base {
get<K extends keyof this>(prop: K) {
return this[prop];
}
set<K extends keyof this>(prop: K, value: this[K]) {
this[prop] = value;
}
}

class Person extends Base {
parts: number;
constructor(parts: number) {
super();
this.set("parts", parts);
}
getParts() {
return this.get("parts")
}
}

class OtherPerson {
parts: number;
constructor(parts: number) {
setProperty(this, "parts", parts);
}
getParts() {
return getProperty(this, "parts")
}
}

//// [keyofAndIndexedAccess.js]
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Shape = (function () {
function Shape() {
}
return Shape;
}());
var TaggedShape = (function (_super) {
__extends(TaggedShape, _super);
function TaggedShape() {
return _super.apply(this, arguments) || this;
}
return TaggedShape;
}(Shape));
var Item = (function () {
function Item() {
}
Expand Down Expand Up @@ -249,6 +308,15 @@ function f32(key) {
var shape = { name: "foo", width: 5, height: 10, visible: true };
return shape[key]; // Shape[K]
}
function f33(shape, key) {
var name = getProperty(shape, "name");
var prop = getProperty(shape, key);
return prop;
}
function f34(ts) {
var tag1 = f33(ts, "tag");
var tag2 = getProperty(ts, "tag");
}
var C = (function () {
function C() {
}
Expand All @@ -261,6 +329,39 @@ function f40(c) {
var y = c["y"];
var z = c["z"];
}
// Repros from #12011
var Base = (function () {
function Base() {
}
Base.prototype.get = function (prop) {
return this[prop];
};
Base.prototype.set = function (prop, value) {
this[prop] = value;
};
return Base;
}());
var Person = (function (_super) {
__extends(Person, _super);
function Person(parts) {
var _this = _super.call(this) || this;
_this.set("parts", parts);
return _this;
}
Person.prototype.getParts = function () {
return this.get("parts");
};
return Person;
}(Base));
var OtherPerson = (function () {
function OtherPerson(parts) {
setProperty(this, "parts", parts);
}
OtherPerson.prototype.getParts = function () {
return getProperty(this, "parts");
};
return OtherPerson;
}());


//// [keyofAndIndexedAccess.d.ts]
Expand All @@ -270,6 +371,9 @@ declare class Shape {
height: number;
visible: boolean;
}
declare class TaggedShape extends Shape {
tag: string;
}
declare class Item {
name: string;
price: number;
Expand Down Expand Up @@ -342,9 +446,25 @@ declare function pluck<T, K extends keyof T>(array: T[], key: K): T[K][];
declare function f30(shapes: Shape[]): void;
declare function f31<K extends keyof Shape>(key: K): Shape[K];
declare function f32<K extends "width" | "height">(key: K): Shape[K];
declare function f33<S extends Shape, K extends keyof S>(shape: S, key: K): S[K];
declare function f34(ts: TaggedShape): void;
declare class C {
x: string;
protected y: string;
private z;
}
declare function f40(c: C): void;
declare class Base {
get<K extends keyof this>(prop: K): this[K];
set<K extends keyof this>(prop: K, value: this[K]): void;
}
declare class Person extends Base {
parts: number;
constructor(parts: number);
getParts(): number;
}
declare class OtherPerson {
parts: number;
constructor(parts: number);
getParts(): number;
}
Loading