Skip to content

Commit

Permalink
Better typings for Array.map()
Browse files Browse the repository at this point in the history
  • Loading branch information
jablko committed Oct 8, 2019
1 parent f1c0300 commit b6e11d6
Show file tree
Hide file tree
Showing 23 changed files with 444 additions and 130 deletions.
4 changes: 2 additions & 2 deletions src/lib/es5.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1138,7 +1138,7 @@ interface ReadonlyArray<T> {
* @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
map<U>(callbackfn: (value: T, index: number, array: readonly T[]) => U, thisArg?: any): U[];
map<U>(callbackfn: (value: T, index: number, array: readonly T[]) => U, thisArg?: any): { -readonly [P in keyof this]: U };
/**
* Returns the elements of an array that meet the condition specified in a callback function.
* @param callbackfn A function that accepts up to three arguments. The filter method calls the callbackfn function one time for each element in the array.
Expand Down Expand Up @@ -1308,7 +1308,7 @@ interface Array<T> {
* @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): { -readonly [P in keyof this]: U };
/**
* Returns the elements of an array that meet the condition specified in a callback function.
* @param callbackfn A function that accepts up to three arguments. The filter method calls the callbackfn function one time for each element in the array.
Expand Down
124 changes: 124 additions & 0 deletions tests/baselines/reference/APISample_jsdoc.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
tests/cases/compiler/APISample_jsdoc.ts(79,32): error TS2495: Type '{ [x: number]: readonly JSDocParameterTag[]; hasTrailingComma?: readonly JSDocParameterTag[] | undefined; length: readonly JSDocParameterTag[]; toString: readonly JSDocParameterTag[]; toLocaleString: readonly JSDocParameterTag[]; concat: readonly JSDocParameterTag[]; join: readonly JSDocParameterTag[]; slice: readonly JSDocParameterTag[]; indexOf: readonly JSDocParameterTag[]; lastIndexOf: readonly JSDocParameterTag[]; every: readonly JSDocParameterTag[]; some: readonly JSDocParameterTag[]; forEach: readonly JSDocParameterTag[]; map: readonly JSDocParameterTag[]; filter: readonly JSDocParameterTag[]; reduce: readonly JSDocParameterTag[]; reduceRight: readonly JSDocParameterTag[]; pos: readonly JSDocParameterTag[]; end: readonly JSDocParameterTag[]; }' is not an array type or a string type.


==== tests/cases/compiler/node_modules/typescript/index.d.ts (0 errors) ====
declare module "typescript" {
export = ts;
}

==== tests/cases/compiler/APISample_jsdoc.ts (1 errors) ====
/*
* Note: This test is a public API sample. The original sources can be found
* at: https://github.com/YousefED/typescript-json-schema
* https://github.com/vega/ts-json-schema-generator
* Please log a "breaking change" issue for any API breaking change affecting this issue
*/

declare var console: any;

import * as ts from "typescript";

// excerpted from https://github.com/YousefED/typescript-json-schema
// (converted from a method and modified; for example, `this: any` to compensate, among other changes)
function parseCommentsIntoDefinition(this: any,
symbol: ts.Symbol,
definition: {description?: string, [s: string]: string | undefined},
otherAnnotations: { [s: string]: true}): void {
if (!symbol) {
return;
}

// the comments for a symbol
let comments = symbol.getDocumentationComment(undefined);

if (comments.length) {
definition.description = comments.map(comment => comment.kind === "lineBreak" ? comment.text : comment.text.trim().replace(/\r\n/g, "\n")).join("");
}

// jsdocs are separate from comments
const jsdocs = symbol.getJsDocTags();
jsdocs.forEach(doc => {
// if we have @TJS-... annotations, we have to parse them
const { name, text } = doc;
if (this.userValidationKeywords[name]) {
definition[name] = this.parseValue(text);
} else {
// special annotations
otherAnnotations[doc.name] = true;
}
});
}


// excerpted from https://github.com/vega/ts-json-schema-generator
export interface Annotations {
[name: string]: any;
}
function getAnnotations(this: any, node: ts.Node): Annotations | undefined {
const symbol: ts.Symbol = (node as any).symbol;
if (!symbol) {
return undefined;
}

const jsDocTags: ts.JSDocTagInfo[] = symbol.getJsDocTags();
if (!jsDocTags || !jsDocTags.length) {
return undefined;
}

const annotations: Annotations = jsDocTags.reduce((result: Annotations, jsDocTag: ts.JSDocTagInfo) => {
const value = this.parseJsDocTag(jsDocTag);
if (value !== undefined) {
result[jsDocTag.name] = value;
}

return result;
}, {});
return Object.keys(annotations).length ? annotations : undefined;
}

// these examples are artificial and mostly nonsensical
function parseSpecificTags(node: ts.Node) {
if (node.kind === ts.SyntaxKind.Parameter) {
return ts.getJSDocParameterTags(node as ts.ParameterDeclaration);
}
if (node.kind === ts.SyntaxKind.FunctionDeclaration) {
const func = node as ts.FunctionDeclaration;
if (ts.hasJSDocParameterTags(func)) {
const flat: ts.JSDocTag[] = [];
for (const tags of func.parameters.map(ts.getJSDocParameterTags)) {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2495: Type '{ [x: number]: readonly JSDocParameterTag[]; hasTrailingComma?: readonly JSDocParameterTag[] | undefined; length: readonly JSDocParameterTag[]; toString: readonly JSDocParameterTag[]; toLocaleString: readonly JSDocParameterTag[]; concat: readonly JSDocParameterTag[]; join: readonly JSDocParameterTag[]; slice: readonly JSDocParameterTag[]; indexOf: readonly JSDocParameterTag[]; lastIndexOf: readonly JSDocParameterTag[]; every: readonly JSDocParameterTag[]; some: readonly JSDocParameterTag[]; forEach: readonly JSDocParameterTag[]; map: readonly JSDocParameterTag[]; filter: readonly JSDocParameterTag[]; reduce: readonly JSDocParameterTag[]; reduceRight: readonly JSDocParameterTag[]; pos: readonly JSDocParameterTag[]; end: readonly JSDocParameterTag[]; }' is not an array type or a string type.
if (tags) flat.push(...tags);
}
return flat;
}
}
}

function getReturnTypeFromJSDoc(node: ts.Node) {
if (node.kind === ts.SyntaxKind.FunctionDeclaration) {
return ts.getJSDocReturnType(node);
}
let type = ts.getJSDocType(node);
if (type && type.kind === ts.SyntaxKind.FunctionType) {
return (type as ts.FunctionTypeNode).type;
}
}

function getAllTags(node: ts.Node) {
ts.getJSDocTags(node);
}

function getSomeOtherTags(node: ts.Node) {
const tags: (ts.JSDocTag | undefined)[] = [];
tags.push(ts.getJSDocAugmentsTag(node));
tags.push(ts.getJSDocClassTag(node));
tags.push(ts.getJSDocReturnTag(node));
const type = ts.getJSDocTypeTag(node);
if (type) {
tags.push(type);
}
tags.push(ts.getJSDocTemplateTag(node));
return tags;
}

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(15,12): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'.
tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(16,5): error TS2461: Type 'StrNum' is not an array type.
tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(17,5): error TS2461: Type '{ 0: string; 1: number; length: 2; }' is not an array type.
tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(18,5): error TS2741: Property '2' is missing in type '[string, number]' but required in type '[number, number, number]'.
tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(19,5): error TS2741: Property '2' is missing in type 'StrNum' but required in type '[number, number, number]'.
Expand Down Expand Up @@ -28,7 +29,7 @@ tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(31,5): error
tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(32,5): error TS2740: Type '{ 0: string; 1: number; length: 2; }' is missing the following properties from type '[number, string]': pop, push, concat, join, and 15 more.


==== tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts (17 errors) ====
==== tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts (18 errors) ====
interface StrNum extends Array<string|number> {
0: string;
1: number;
Expand All @@ -47,6 +48,8 @@ tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(32,5): error
~
!!! error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'.
var [d, e, f] = y;
~~~~~~~~~
!!! error TS2461: Type 'StrNum' is not an array type.
var [g, h, i] = z;
~~~~~~~~~
!!! error TS2461: Type '{ 0: string; 1: number; length: 2; }' is not an array type.
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/arityAndOrderCompatibility01.types
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ var [a, b, c] = x;
>x : [string, number]

var [d, e, f] = y;
>d : string
>e : number
>f : string | number
>d : any
>e : any
>f : any
>y : StrNum

var [g, h, i] = z;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(13,1): error TS2322: Type 'A[]' is not assignable to type 'readonly B[]'.
Property 'b' is missing in type 'A' but required in type 'B'.
tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(15,1): error TS2322: Type 'C<A>' is not assignable to type 'readonly A[]'.
The types of 'map(...).length' are incompatible between these types.
Type 'U' is not assignable to type 'number'.
tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(16,1): error TS2322: Type 'C<B>' is not assignable to type 'readonly A[]'.
The types of 'map(...).length' are incompatible between these types.
Type 'U' is not assignable to type 'number'.
tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(17,1): error TS2322: Type 'C<B>' is not assignable to type 'readonly B[]'.
The types returned by 'map(...)' are incompatible between these types.
Type '{ [x: number]: U; c: U; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }' is not assignable to type 'U[]'.
tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(18,1): error TS2322: Type 'C<A>' is not assignable to type 'readonly B[]'.
The types returned by 'concat(...)' are incompatible between these types.
Type 'A[]' is not assignable to type 'B[]'.
Type 'A' is not assignable to type 'B'.


==== tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts (2 errors) ====
==== tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts (5 errors) ====
class A { a }
class B extends A { b }
class C<T> extends Array<T> { c }
Expand All @@ -26,8 +35,20 @@ tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(18,1): error T
!!! related TS2728 tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts:2:21: 'b' is declared here.

rra = cra;
~~~
!!! error TS2322: Type 'C<A>' is not assignable to type 'readonly A[]'.
!!! error TS2322: The types of 'map(...).length' are incompatible between these types.
!!! error TS2322: Type 'U' is not assignable to type 'number'.
rra = crb; // OK, C<B> is assignable to ReadonlyArray<A>
~~~
!!! error TS2322: Type 'C<B>' is not assignable to type 'readonly A[]'.
!!! error TS2322: The types of 'map(...).length' are incompatible between these types.
!!! error TS2322: Type 'U' is not assignable to type 'number'.
rrb = crb;
~~~
!!! error TS2322: Type 'C<B>' is not assignable to type 'readonly B[]'.
!!! error TS2322: The types returned by 'map(...)' are incompatible between these types.
!!! error TS2322: Type '{ [x: number]: U; c: U; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }' is not assignable to type 'U[]'.
rrb = cra; // error: 'A' is not assignable to 'B'
~~~
!!! error TS2322: Type 'C<A>' is not assignable to type 'readonly B[]'.
Expand Down
36 changes: 36 additions & 0 deletions tests/baselines/reference/bestChoiceType.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
tests/cases/compiler/bestChoiceType.ts(3,23): error TS2349: This expression is not callable.
Each member of the union type '(<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => { [x: number]: U; index?: U | undefined; input?: U | undefined; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other.
tests/cases/compiler/bestChoiceType.ts(10,15): error TS2349: This expression is not callable.
Each member of the union type '(<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => { [x: number]: U; index?: U | undefined; input?: U | undefined; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other.
tests/cases/compiler/bestChoiceType.ts(16,15): error TS2349: This expression is not callable.
Each member of the union type '(<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => { [x: number]: U; index?: U | undefined; input?: U | undefined; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other.


==== tests/cases/compiler/bestChoiceType.ts (3 errors) ====
// Repro from #10041

(''.match(/ /) || []).map(s => s.toLowerCase());
~~~
!!! error TS2349: This expression is not callable.
!!! error TS2349: Each member of the union type '(<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => { [x: number]: U; index?: U | undefined; input?: U | undefined; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other.

// Similar cases

function f1() {
let x = ''.match(/ /);
let y = x || [];
let z = y.map(s => s.toLowerCase());
~~~
!!! error TS2349: This expression is not callable.
!!! error TS2349: Each member of the union type '(<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => { [x: number]: U; index?: U | undefined; input?: U | undefined; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other.
}

function f2() {
let x = ''.match(/ /);
let y = x ? x : [];
let z = y.map(s => s.toLowerCase());
~~~
!!! error TS2349: This expression is not callable.
!!! error TS2349: Each member of the union type '(<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => { [x: number]: U; index?: U | undefined; input?: U | undefined; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other.
}

Loading

0 comments on commit b6e11d6

Please sign in to comment.