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

Improve reverse mapped types #31221

Merged
merged 5 commits into from May 10, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/compiler/checker.ts
Expand Up @@ -14887,10 +14887,22 @@ namespace ts {
return type;
}

// We consider a type to be partially inferable if it isn't marked non-inferable or if it is
// an object literal type with at least one property of an inferable type. For example, an object
// literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive
// arrow function, but is considered partially inferable because property 'a' has an inferable type.
function isPartiallyInferableType(type: Type): boolean {
return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) ||
isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop)));
}

function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) {
// If any property contains context sensitive functions that have been skipped, the source type
// is incomplete and we can't infer a meaningful input type.
if (getObjectFlags(source) & ObjectFlags.NonInferrableType || getPropertiesOfType(source).length === 0 && !getIndexInfoOfType(source, IndexKind.String)) {
// We consider a source type reverse mappable if it has a string index signature or if
// it has one or more properties and all properties have inferable types.
const properties = getPropertiesOfType(source);
const isReverseMappable = getIndexInfoOfType(source, IndexKind.String) ||
properties.length !== 0 && every(properties, prop => isPartiallyInferableType(getTypeOfSymbol(prop)));
weswigham marked this conversation as resolved.
Show resolved Hide resolved
if (!isReverseMappable) {
return undefined;
}
// For arrays and tuples we infer new arrays and tuples where the reverse mapping has been
Expand Down
@@ -0,0 +1,101 @@
tests/cases/compiler/reverseMappedPartiallyInferableTypes.ts(91,20): error TS2571: Object is of type 'unknown'.


==== tests/cases/compiler/reverseMappedPartiallyInferableTypes.ts (1 errors) ====
// Repro from #30505

export type Prop<T> = { (): T }
export type PropType<T> = Prop<T>;
export type PropDefaultValue<T> = T;


export type PropValidatorFunction<T> = (value: T) => boolean;
export type PropValidator<T> = PropOptions<T>;


export type PropOptions<T> = {
type: PropType<T>;

value?: PropDefaultValue<T>,
required?: boolean;
validator?: PropValidatorFunction<T>;
}

export type RecordPropsDefinition<T> = {
[K in keyof T]: PropValidator<T[K]>
}
export type PropsDefinition<T> = RecordPropsDefinition<T>;


declare function extend<T>({ props }: { props: PropsDefinition<T> }): PropsDefinition<T>;

interface MyType {
valid: boolean;
}

const r = extend({
props: {
notResolved: {
type: Object as PropType<MyType>,
validator: x => {
return x.valid;
}
},
explicit: {
type: Object as PropType<MyType>,
validator: (x: MyType) => {
return x.valid;
}
}
}
})

r.explicit
r.notResolved
r.explicit.required
r.notResolved.required

// Modified repro from #30505

type Box<T> = {
contents?: T;
contains?(content: T): boolean;
};

type Mapped<T> = {
[K in keyof T]: Box<T[K]>;
}

declare function id<T>(arg: Mapped<T>): Mapped<T>;

// All properties have inferable types

const obj1 = id({
foo: {
contents: ""
}
});

// Some properties have inferable types

const obj2 = id({
foo: {
contents: "",
contains(k) {
return k.length > 0;
}
}
});

// No properties have inferable types

const obj3 = id({
foo: {
contains(k) {
return k.length > 0;
~
!!! error TS2571: Object is of type 'unknown'.
}
}
});

144 changes: 144 additions & 0 deletions tests/baselines/reference/reverseMappedPartiallyInferableTypes.js
@@ -0,0 +1,144 @@
//// [reverseMappedPartiallyInferableTypes.ts]
// Repro from #30505

export type Prop<T> = { (): T }
export type PropType<T> = Prop<T>;
export type PropDefaultValue<T> = T;


export type PropValidatorFunction<T> = (value: T) => boolean;
export type PropValidator<T> = PropOptions<T>;


export type PropOptions<T> = {
type: PropType<T>;

value?: PropDefaultValue<T>,
required?: boolean;
validator?: PropValidatorFunction<T>;
}

export type RecordPropsDefinition<T> = {
[K in keyof T]: PropValidator<T[K]>
}
export type PropsDefinition<T> = RecordPropsDefinition<T>;


declare function extend<T>({ props }: { props: PropsDefinition<T> }): PropsDefinition<T>;

interface MyType {
valid: boolean;
}

const r = extend({
props: {
notResolved: {
type: Object as PropType<MyType>,
validator: x => {
return x.valid;
}
},
explicit: {
type: Object as PropType<MyType>,
validator: (x: MyType) => {
return x.valid;
}
}
}
})

r.explicit
r.notResolved
r.explicit.required
r.notResolved.required

// Modified repro from #30505

type Box<T> = {
contents?: T;
contains?(content: T): boolean;
};

type Mapped<T> = {
[K in keyof T]: Box<T[K]>;
}

declare function id<T>(arg: Mapped<T>): Mapped<T>;

// All properties have inferable types

const obj1 = id({
foo: {
contents: ""
}
});

// Some properties have inferable types

const obj2 = id({
foo: {
contents: "",
contains(k) {
return k.length > 0;
}
}
});

// No properties have inferable types

const obj3 = id({
foo: {
contains(k) {
return k.length > 0;
}
}
});


//// [reverseMappedPartiallyInferableTypes.js]
"use strict";
// Repro from #30505
exports.__esModule = true;
var r = extend({
props: {
notResolved: {
type: Object,
validator: function (x) {
return x.valid;
}
},
explicit: {
type: Object,
validator: function (x) {
return x.valid;
}
}
}
});
r.explicit;
r.notResolved;
r.explicit.required;
r.notResolved.required;
// All properties have inferable types
var obj1 = id({
foo: {
contents: ""
}
});
// Some properties have inferable types
var obj2 = id({
foo: {
contents: "",
contains: function (k) {
return k.length > 0;
}
}
});
// No properties have inferable types
var obj3 = id({
foo: {
contains: function (k) {
return k.length > 0;
}
}
});