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

Generic defaults implicit any #14396

Closed
wants to merge 7 commits into from
Closed
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
105 changes: 70 additions & 35 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4590,7 +4590,7 @@ namespace ts {
const minTypeArgumentCount = getMinTypeArgumentCount(baseSig.typeParameters);
const typeParamCount = length(baseSig.typeParameters);
if (typeArgCount >= minTypeArgumentCount && typeArgCount <= typeParamCount) {
const sig = typeParamCount ? createSignatureInstantiation(baseSig, fillMissingTypeArguments(typeArguments, baseSig.typeParameters, minTypeArgumentCount)) : cloneSignature(baseSig);
const sig = typeParamCount ? createSignatureInstantiation(baseSig, fillMissingTypeArguments(typeArguments, baseSig.typeParameters, /*errorNode*/ undefined)) : cloneSignature(baseSig);
sig.typeParameters = classType.localTypeParameters;
sig.resolvedReturnType = classType;
result.push(sig);
Expand Down Expand Up @@ -5417,33 +5417,77 @@ namespace ts {
*
* @param typeArguments The supplied type arguments.
* @param typeParameters The requested type parameters.
* @param minTypeArgumentCount The minimum number of required type arguments.
* @param errorNode The node on which to report any implicit `any` errors.
*/
function fillMissingTypeArguments(typeArguments: Type[] | undefined, typeParameters: TypeParameter[] | undefined, minTypeArgumentCount: number) {
function fillMissingTypeArguments(typeArguments: Type[] | undefined, typeParameters: TypeParameter[] | undefined, errorNode: Node) {
const numTypeParameters = length(typeParameters);
if (numTypeParameters) {
const numTypeArguments = length(typeArguments);
if (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters) {
if (!typeArguments) {
typeArguments = [];
}

if (numTypeArguments <= numTypeParameters) {
// Map an unsatisfied type parameter with a default type.
// If a type parameter does not have a default type, or if the default type
// is a forward reference, the empty object type is used.
const context = createFillContext(typeParameters, typeArguments);
for (let i = numTypeArguments; i < numTypeParameters; i++) {
typeArguments[i] = emptyObjectType;
}
for (let i = numTypeArguments; i < numTypeParameters; i++) {
const mapper = createTypeMapper(typeParameters, typeArguments);
const defaultType = getDefaultFromTypeParameter(typeParameters[i]);
typeArguments[i] = defaultType ? instantiateType(defaultType, mapper) : emptyObjectType;
fillTypeArgument(context, i);
if (errorNode && context.failed && compilerOptions.noImplicitAny) {
error(errorNode,
Diagnostics.Type_parameter_0_implicitly_has_type_any_because_it_was_not_supplied,
typeToString(typeParameters[i]));
}
}

return context.typeArguments;
}
}
return typeArguments;
}

function createFillContext(typeParameters: TypeParameter[], typeArguments: Type[]): FillContext {
return {
typeParameters,
typeArguments: typeArguments || [],
};
}

function fillTypeArgument(context: FillContext, index: number) {
const defaultType = getDefaultFromTypeParameter(context.typeParameters[index]);
if (defaultType) {
context.failed = false;
context.typeParameterIndex = index;
context.typeArguments[index] = instantiateType(defaultType, getFillMapper(context));
}
else {
context.failed = true;
context.typeArguments[index] = anyType;
}
}

function getFillMapper(context: FillContext): TypeMapper {
if (!context.mapper) {
const mapper: TypeMapper = t => {
const typeParameters = context.typeParameters;
for (let i = 0; i < typeParameters.length; i++) {
if (t === typeParameters[i]) {
return getDefaultType(context, i);
}
}
};
mapper.mappedTypes = context.typeParameters;
context.mapper = mapper;
}
return context.mapper;
}

function getDefaultType(context: FillContext, index: number) {
if (index < context.typeParameterIndex) {
return context.typeArguments[index];
}

context.failed = true;
return anyType;
}

function getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature {
const links = getNodeLinks(declaration);
if (!links.resolvedSignature) {
Expand Down Expand Up @@ -5643,7 +5687,7 @@ namespace ts {
}

function getSignatureInstantiation(signature: Signature, typeArguments: Type[]): Signature {
typeArguments = fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters));
typeArguments = fillMissingTypeArguments(typeArguments, signature.typeParameters, /*errorNode*/ undefined);
const instantiations = signature.instantiations || (signature.instantiations = createMap<Signature>());
const id = getTypeListId(typeArguments);
let instantiation = instantiations.get(id);
Expand Down Expand Up @@ -5810,21 +5854,17 @@ namespace ts {
const typeParameters = type.localTypeParameters;
if (typeParameters) {
const numTypeArguments = length(node.typeArguments);
const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters);
if (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length) {
if (numTypeArguments > typeParameters.length) {
error(node,
minTypeArgumentCount === typeParameters.length
? Diagnostics.Generic_type_0_requires_1_type_argument_s
: Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments,
Diagnostics.Generic_type_0_requires_1_or_fewer_type_argument_s,
typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType),
minTypeArgumentCount,
typeParameters.length);
return unknownType;
}
// In a type reference, the outer type parameters of the referenced class or interface are automatically
// supplied as type arguments and the type reference only specifies arguments for the local type parameters
// of the class or interface.
const typeArguments = concatenate(type.outerTypeParameters, fillMissingTypeArguments(map(node.typeArguments, getTypeFromTypeNode), typeParameters, minTypeArgumentCount));
const typeArguments = concatenate(type.outerTypeParameters, fillMissingTypeArguments(map(node.typeArguments, getTypeFromTypeNode), typeParameters, node));
return createTypeReference(<GenericType>type, typeArguments);
}
if (node.typeArguments) {
Expand All @@ -5834,14 +5874,14 @@ namespace ts {
return type;
}

function getTypeAliasInstantiation(symbol: Symbol, typeArguments: Type[]): Type {
function getTypeAliasInstantiation(symbol: Symbol, typeArguments: Type[], errorNode: Node): Type {
const type = getDeclaredTypeOfSymbol(symbol);
const links = getSymbolLinks(symbol);
const typeParameters = links.typeParameters;
const id = getTypeListId(typeArguments);
let instantiation = links.instantiations.get(id);
if (!instantiation) {
links.instantiations.set(id, instantiation = instantiateTypeNoAlias(type, createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters)))));
links.instantiations.set(id, instantiation = instantiateTypeNoAlias(type, createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, errorNode))));
}
return instantiation;
}
Expand All @@ -5854,19 +5894,15 @@ namespace ts {
const typeParameters = getSymbolLinks(symbol).typeParameters;
if (typeParameters) {
const numTypeArguments = length(node.typeArguments);
const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters);
if (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length) {
if (numTypeArguments > typeParameters.length) {
error(node,
minTypeArgumentCount === typeParameters.length
? Diagnostics.Generic_type_0_requires_1_type_argument_s
: Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments,
Diagnostics.Generic_type_0_requires_1_or_fewer_type_argument_s,
symbolToString(symbol),
minTypeArgumentCount,
typeParameters.length);
return unknownType;
}
const typeArguments = map(node.typeArguments, getTypeFromTypeNode);
return getTypeAliasInstantiation(symbol, typeArguments);
return getTypeAliasInstantiation(symbol, typeArguments, node);
}
if (node.typeArguments) {
error(node, Diagnostics.Type_0_is_not_generic, symbolToString(symbol));
Expand Down Expand Up @@ -7269,7 +7305,7 @@ namespace ts {
// instantiated for T. Instead, we need to further instantiate the { x: U, t: U } form.
if (type.aliasSymbol && isTopLevelTypeAlias(type.aliasSymbol)) {
if (type.aliasTypeArguments) {
return getTypeAliasInstantiation(type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
return getTypeAliasInstantiation(type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper), /*errorNode*/ undefined);
}
return type;
}
Expand Down Expand Up @@ -14378,7 +14414,7 @@ namespace ts {
if (candidate.typeParameters) {
let typeArgumentTypes: Type[] | undefined;
if (typeArguments) {
typeArgumentTypes = fillMissingTypeArguments(map(typeArguments, getTypeFromTypeNode), candidate.typeParameters, getMinTypeArgumentCount(candidate.typeParameters));
typeArgumentTypes = fillMissingTypeArguments(map(typeArguments, getTypeFromTypeNode), candidate.typeParameters, /*errorNode*/ undefined);
typeArgumentsAreValid = checkTypeArguments(candidate, typeArguments, typeArgumentTypes, /*reportErrors*/ false);
}
else {
Expand Down Expand Up @@ -17054,15 +17090,14 @@ namespace ts {
}

function checkTypeArgumentConstraints(typeParameters: TypeParameter[], typeArgumentNodes: TypeNode[]): boolean {
const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters);
let typeArguments: Type[];
let mapper: TypeMapper;
let result = true;
for (let i = 0; i < typeParameters.length; i++) {
const constraint = getConstraintOfTypeParameter(typeParameters[i]);
if (constraint) {
if (!typeArguments) {
typeArguments = fillMissingTypeArguments(map(typeArgumentNodes, getTypeFromTypeNode), typeParameters, minTypeArgumentCount);
typeArguments = fillMissingTypeArguments(map(typeArgumentNodes, getTypeFromTypeNode), typeParameters, /*errorNode*/ undefined);
mapper = createTypeMapper(typeParameters, typeArguments);
}
const typeArgument = typeArguments[i];
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@
"category": "Error",
"code": 2313
},
"Generic type '{0}' requires {1} type argument(s).": {
"Generic type '{0}' requires {1} or fewer type argument(s).": {
"category": "Error",
"code": 2314
},
Expand Down Expand Up @@ -2087,7 +2087,7 @@
"category": "Error",
"code": 2706
},
"Generic type '{0}' requires between {1} and {2} type arguments.": {
"Type parameter '{0}' implicitly has type 'any' because it was not supplied.": {
"category": "Error",
"code": 2707
},
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3238,6 +3238,15 @@ namespace ts {
useAnyForNoInferences?: boolean; // Use any instead of {} for no inferences
}

/* @internal */
export interface FillContext {
typeParameters: TypeParameter[]; // Type parameters for which defaults are filled
typeParameterIndex?: number; // Index of the type parameter being filled.
typeArguments: Type[]; // Filled type arguments for each type parameter.
mapper?: TypeMapper; // Type mapper for this fill context
failed?: boolean; // Indicates whether the current type parameter failed.
}

/* @internal */
export const enum SpecialPropertyAssignmentKind {
None,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
tests/cases/compiler/arrayLiteralAndArrayConstructorEquivalence1.ts(3,14): error TS2314: Generic type 'Array<T>' requires 1 type argument(s).
tests/cases/compiler/arrayLiteralAndArrayConstructorEquivalence1.ts(3,14): error TS2707: Type parameter 'T' implicitly has type 'any' because it was not supplied.


==== tests/cases/compiler/arrayLiteralAndArrayConstructorEquivalence1.ts (1 errors) ====
var myCars=new Array();
var myCars=new Array();
var myCars3 = new Array({});
var myCars4: Array; // error
~~~~~
!!! error TS2314: Generic type 'Array<T>' requires 1 type argument(s).
!!! error TS2707: Type parameter 'T' implicitly has type 'any' because it was not supplied.
var myCars5: Array<any>[];

myCars = myCars3;
myCars = myCars4;
myCars = myCars5;

myCars3 = myCars;
myCars3 = myCars4;
myCars3 = myCars5;
myCars3 = myCars5;

Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
//// [arrayLiteralAndArrayConstructorEquivalence1.ts]
var myCars=new Array();
var myCars=new Array();
var myCars3 = new Array({});
var myCars4: Array; // error
var myCars5: Array<any>[];

myCars = myCars3;
myCars = myCars4;
myCars = myCars5;

myCars3 = myCars;
myCars3 = myCars4;
myCars3 = myCars5;
myCars3 = myCars5;


//// [arrayLiteralAndArrayConstructorEquivalence1.js]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
tests/cases/compiler/arrayReferenceWithoutTypeArgs.ts(2,17): error TS2314: Generic type 'Array<T>' requires 1 type argument(s).
tests/cases/compiler/arrayReferenceWithoutTypeArgs.ts(2,17): error TS2707: Type parameter 'T' implicitly has type 'any' because it was not supplied.


==== tests/cases/compiler/arrayReferenceWithoutTypeArgs.ts (1 errors) ====
class X {
public f(a: Array) { }
~~~~~
!!! error TS2314: Generic type 'Array<T>' requires 1 type argument(s).
!!! error TS2707: Type parameter 'T' implicitly has type 'any' because it was not supplied.
}
4 changes: 2 additions & 2 deletions tests/baselines/reference/emptyGenericParamList.errors.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
tests/cases/compiler/emptyGenericParamList.ts(2,8): error TS2314: Generic type 'I<T>' requires 1 type argument(s).
tests/cases/compiler/emptyGenericParamList.ts(2,8): error TS2707: Type parameter 'T' implicitly has type 'any' because it was not supplied.
tests/cases/compiler/emptyGenericParamList.ts(2,9): error TS1099: Type argument list cannot be empty.


==== tests/cases/compiler/emptyGenericParamList.ts (2 errors) ====
class I<T> {}
var x: I<>;
~~~
!!! error TS2314: Generic type 'I<T>' requires 1 type argument(s).
!!! error TS2707: Type parameter 'T' implicitly has type 'any' because it was not supplied.
~~
!!! error TS1099: Type argument list cannot be empty.
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
tests/cases/compiler/externalModuleExportingGenericClass_file1.ts(2,8): error TS2314: Generic type 'C<T>' requires 1 type argument(s).
tests/cases/compiler/externalModuleExportingGenericClass_file1.ts(2,8): error TS2707: Type parameter 'T' implicitly has type 'any' because it was not supplied.


==== tests/cases/compiler/externalModuleExportingGenericClass_file1.ts (1 errors) ====
import a = require('./externalModuleExportingGenericClass_file0');
var v: a; // this should report error
~
!!! error TS2314: Generic type 'C<T>' requires 1 type argument(s).
!!! error TS2707: Type parameter 'T' implicitly has type 'any' because it was not supplied.
var v2: any = (new a()).foo;
var v3: number = (new a<number>()).foo;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
tests/cases/compiler/genericArrayAssignmentCompatErrors.ts(2,15): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.
tests/cases/compiler/genericArrayAssignmentCompatErrors.ts(4,14): error TS2314: Generic type 'Array<T>' requires 1 type argument(s).
tests/cases/compiler/genericArrayAssignmentCompatErrors.ts(4,14): error TS2707: Type parameter 'T' implicitly has type 'any' because it was not supplied.


==== tests/cases/compiler/genericArrayAssignmentCompatErrors.ts (2 errors) ====
var myCars=new Array();
var myCars=new Array();
var myCars2 = new [];
~~~~~~
!!! error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.
var myCars3 = new Array({});
var myCars4: Array; // error
~~~~~
!!! error TS2314: Generic type 'Array<T>' requires 1 type argument(s).
!!! error TS2707: Type parameter 'T' implicitly has type 'any' because it was not supplied.
var myCars5: Array<any>[];

myCars = myCars2;
myCars = myCars3;
myCars = myCars4;
myCars = myCars5;

myCars2 = myCars;
myCars2 = myCars3;
myCars2 = myCars4;
myCars2 = myCars5;

myCars3 = myCars;
myCars3 = myCars2;
myCars3 = myCars4;
myCars3 = myCars5;
myCars3 = myCars5;

Loading